Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
018fc0e871 | ||
|
|
1111d24333 | ||
|
|
d8aee9284d | ||
|
|
7121dacbbd | ||
|
|
0528e8bbbe | ||
|
|
458e43252d | ||
|
|
fd0a8a3d0e |
@@ -1,3 +1,63 @@
|
||||
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.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.43
|
||||
#Quisk version 4.2.49
|
||||
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)
|
||||
|
||||
+18
-25
@@ -88,6 +88,7 @@ class Configuration:
|
||||
platform_ignore = 'win_'
|
||||
self.sections = []
|
||||
self.receiver_data = []
|
||||
self.receiver_explain = []
|
||||
self.StatePath = conf.settings_file_path
|
||||
if not self.StatePath:
|
||||
self.StatePath = os.path.join(app.QuiskFilesDir, "quisk_settings.json")
|
||||
@@ -158,9 +159,9 @@ class Configuration:
|
||||
dlg.Destroy()
|
||||
else:
|
||||
radio_dict["use_soapy"] = '0'
|
||||
if radio_type not in ("HiQSDR", "Hermes", "Red Pitaya", "Odyssey", "Odyssey2"):
|
||||
if radio_type not in ("HiQSDR", "Hermes", "Red Pitaya", "Odyssey", "Odyssey2", "OpenHPSDR"):
|
||||
radio_dict["use_rx_udp"] = '0'
|
||||
if radio_type in ("Hermes", "Red Pitaya", "Odyssey2"):
|
||||
if radio_type in ("Hermes", "Red Pitaya", "Odyssey2", "OpenHPSDR"):
|
||||
if "Hermes_BandDict" not in radio_dict:
|
||||
radio_dict["Hermes_BandDict"] = {}
|
||||
if "Hermes_BandDictTx" not in radio_dict:
|
||||
@@ -601,6 +602,7 @@ class Configuration:
|
||||
# Receiver sections start with 16 #, "Receivers ", receiver name, explain
|
||||
# self.receiver_data is a list of [receiver_name, receiver_data]
|
||||
# receiver_data is a list of [data_name, text, fmt, help_text, values]
|
||||
# self.receiver_explain is a list of the text in Receivers: the name, a comma and the explanation.
|
||||
|
||||
# Variable names start with ## variable_name variable_text, format
|
||||
# The format is integer, number, text, boolean, integer choice, text choice, rfile
|
||||
@@ -638,6 +640,7 @@ class Configuration:
|
||||
continue
|
||||
if line[0:27] == '################ Receivers ':
|
||||
section = 'Receivers'
|
||||
self.receiver_explain.append(line[27:].strip())
|
||||
args = line[27:].split(',', 1)
|
||||
rxname = args[0].strip()
|
||||
section_data = []
|
||||
@@ -2298,8 +2301,8 @@ class ControlMixin:
|
||||
if self.radio_name == Settings[1]: # changed for current radio
|
||||
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
|
||||
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
|
||||
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable',
|
||||
'fixed_tune_offset'):
|
||||
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec',
|
||||
'fixed_tune_offset', 'calibrate_temp_20', 'calibrate_temp_40', 'calibrate_current_0', 'calibrate_current_1'):
|
||||
setattr(conf, name, x)
|
||||
application.ImmediateChange(name)
|
||||
elif name[0:4] in ('lin_', 'win_'):
|
||||
@@ -2400,6 +2403,7 @@ class BaseWindow(wx.ScrolledWindow, ControlMixin):
|
||||
def __init__(self, parent):
|
||||
wx.ScrolledWindow.__init__(self, parent)
|
||||
ControlMixin.__init__(self, parent)
|
||||
self.SetScrollRate(8, 8)
|
||||
|
||||
class ConfigConfig(BaseWindow):
|
||||
def __init__(self, parent, width):
|
||||
@@ -2510,7 +2514,6 @@ The CW message will then repeat. A time of zero means no repeat.'
|
||||
sl, btn = self.AddTextSliderHelp(4, " Repeat secs %.1f ", 0, 0, 100, self.OnPlayFileRepeat, help_text, span=2, scale=0.1)
|
||||
self.NextRow()
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def MakeFileButton(self, text, path, index, name, help_text):
|
||||
if index < 10: # record buttons
|
||||
cb = self.AddCheckBox(4, text, self.OnCheckRecPlay)
|
||||
@@ -2688,7 +2691,6 @@ class ConfigTxAudio(BaseWindow):
|
||||
application.CtrlTxAudioPreemph = sld
|
||||
self.NextRow()
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnTimer(self, event):
|
||||
if not self.tmp_playing:
|
||||
self.timer.Stop()
|
||||
@@ -2737,8 +2739,8 @@ class Radios(BaseWindow): # The "Radios" first-level page
|
||||
self.NextRow()
|
||||
rb = self.AddRadioButton(1, "Add a new radio with the general type", self.NewTop)
|
||||
choices = []
|
||||
for name, data in local_conf.receiver_data:
|
||||
choices.append(name)
|
||||
for explain in local_conf.receiver_explain:
|
||||
choices.append(explain)
|
||||
choices.sort()
|
||||
self.add_type = self.AddComboCtrl(2, '', choices=choices, no_edit=True)
|
||||
self.add_type.SetSelection(0)
|
||||
@@ -2766,7 +2768,6 @@ class Radios(BaseWindow): # The "Radios" first-level page
|
||||
self.apply_btn = self.AddPushButton(3, "Apply", self.OnBtnApply)
|
||||
self.NewRadioNames()
|
||||
self.NewTop()
|
||||
self.SetScrollRate(1, 1)
|
||||
def NewTop(self, event=None):
|
||||
if event:
|
||||
btn = event.GetEventObject()
|
||||
@@ -2810,7 +2811,7 @@ class Radios(BaseWindow): # The "Radios" first-level page
|
||||
if not name or self.DuplicateName(name):
|
||||
return
|
||||
self.add_name.SetValue('')
|
||||
typ = self.add_type.GetValue().strip()
|
||||
typ = self.add_type.GetValue().split(',')[0].strip()
|
||||
if local_conf.AddRadio(name, typ):
|
||||
self.NewRadioNames()
|
||||
local_conf.settings_changed = True
|
||||
@@ -3003,7 +3004,6 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
|
||||
self.AddColSpacer(2, 20)
|
||||
self.AddColSpacer(5, 20)
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnButtonChangeFavorites(self, event):
|
||||
if isinstance(event, (ChoiceCombo, ComboCtrl)):
|
||||
path = event.GetValue()
|
||||
@@ -3128,7 +3128,6 @@ class RadioHardwareBase(BaseWindow): # The Hardware page in the second-level no
|
||||
self.NextRow(7)
|
||||
self.AddColSpacer(2, 20)
|
||||
self.AddColSpacer(5, 20)
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnButtonChangeHardware(self, event):
|
||||
if isinstance(event, (ChoiceCombo, ComboCtrl)):
|
||||
path = event.GetValue()
|
||||
@@ -3175,25 +3174,24 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
|
||||
data_names = local_conf.GetReceiverData(radio_type)
|
||||
col = 1
|
||||
border = 2
|
||||
hermes_board_id = 0
|
||||
if radio_type == "Hermes":
|
||||
try:
|
||||
hermes_board_id = application.Hardware.hermes_board_id
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
is_HermesLite2 = application.Hardware.is_HermesLite2
|
||||
except:
|
||||
is_HermesLite2 = False
|
||||
if radio_name == Settings[1] and hasattr(application.Hardware, "ProgramGateware"):
|
||||
help_text = "Choose an RBF file and program the Gateware (FPGA software) over Ethernet."
|
||||
self.AddTextButtonHelp(1, "Gateware Update", "Program from RBF file..", application.Hardware.ProgramGateware, help_text)
|
||||
col = 1
|
||||
self.NextRow(self.row + 2)
|
||||
for name, text, fmt, help_text, values in data_names:
|
||||
text = text.replace("\\u2103", "\u2103")
|
||||
if name in ('hardware_file_name', 'widgets_file_name'):
|
||||
pass
|
||||
elif name[0:4] == platform_ignore:
|
||||
pass
|
||||
elif name in ('Hermes_BandDictEnTx', ):
|
||||
pass
|
||||
elif 'Hl2_' in name and hermes_board_id != 6:
|
||||
elif 'Hl2_' in name and not is_HermesLite2:
|
||||
pass
|
||||
elif fmt[0:4] in ('dict', 'list'):
|
||||
pass
|
||||
@@ -3222,7 +3220,7 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
|
||||
col = 1
|
||||
border = 2
|
||||
self.NextRow()
|
||||
if hermes_board_id == 6:
|
||||
if is_HermesLite2:
|
||||
if col == 4:
|
||||
self.NextRow()
|
||||
help_text = ('This controls the bias level for transistors in the final power amplifier. Enter a level from 0 to 255.'
|
||||
@@ -3238,7 +3236,6 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
|
||||
self.HermesWriteBiasButton = self.AddPushButton(7, "Write", self.OnButtonHermesWriteBias, border=0)
|
||||
self.HermesWriteBiasButton.Enable(enbl)
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnHermesChangeBias0(self, event):
|
||||
value = self.HermesBias0.GetValue()
|
||||
application.Hardware.ChangeBias0(value)
|
||||
@@ -3852,7 +3849,6 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
|
||||
break
|
||||
self.gbs = gbs
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def ItemValue(self, row, col):
|
||||
data_name = self.sound_names[row][col]
|
||||
if col == 4: # Device names
|
||||
@@ -4080,7 +4076,6 @@ The Tx bits are sent as the "Alex" filters and are sent to the J16 interface.'
|
||||
self.AddBitField(col + 1, 7, 'Hermes_BandDictTx', band, bus, self.ChangeIO)
|
||||
self.NextRow()
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def SortCmp(self, item1):
|
||||
# Numerical conversion of band name to megahertz
|
||||
try:
|
||||
@@ -4255,7 +4250,6 @@ class RadioRemote(BaseWindow): # The Remote page in the second-level notebook f
|
||||
self.AddColSpacer(3, 20)
|
||||
self.AddColSpacer(6, 20)
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnChangePassword(self, event):
|
||||
text = local_conf.globals.get('remote_radio_password', '')
|
||||
dlg = wx.TextEntryDialog(self, "Please enter a password for remote access",
|
||||
@@ -4337,7 +4331,6 @@ class xxRadioFilters(BaseWindow): # The Filters page in the second-level notebo
|
||||
self.NextRow()
|
||||
index += 1
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnChangeFreq(self, event):
|
||||
freq = event.GetString()
|
||||
radio_dict = local_conf.GetRadioDict(self.radio_name)
|
||||
|
||||
@@ -75,6 +75,7 @@ h3 {font-size:1.6rem;}
|
||||
<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>
|
||||
@@ -199,7 +200,8 @@ If you have Python 3 installed on your computer, just continue to use that versi
|
||||
<b>On Windows Quisk requires Python 3.8, 3.9, 3.10, 3.11, 3.12 or 3.13.</b>
|
||||
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>
|
||||
@@ -1627,6 +1629,24 @@ quisk/wdsp directory and entering "make". The library is used in-place and no in
|
||||
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,681 +1 @@
|
||||
#include <Python.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <complex.h> // Use native C99 complex type for fftw3
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "quisk.h"
|
||||
#include "freedv.h"
|
||||
#include <stdint.h>
|
||||
|
||||
int DEBUG;
|
||||
|
||||
#define MAX_RECEIVERS 2
|
||||
|
||||
typedef struct { // from comp.h
|
||||
float real;
|
||||
float imag;
|
||||
} COMP;
|
||||
|
||||
struct freedv; // from freedv_api.h
|
||||
typedef void (*freedv_callback_rx)(void *, char);
|
||||
typedef char (*freedv_callback_tx)(void *);
|
||||
/* Protocol bits are packed MSB-first */
|
||||
/* Called when a frame containing protocol data is decoded */
|
||||
typedef void (*freedv_callback_protorx)(void *, char *);
|
||||
/* Called when a frame containing protocol data is to be sent */
|
||||
typedef void (*freedv_callback_prototx)(void *, char *);
|
||||
/* Data packet callbacks */
|
||||
/* Called when a packet has been received */
|
||||
typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size);
|
||||
/* Called when a new packet can be send */
|
||||
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size);
|
||||
|
||||
/* advanced freedv open options required by some modes */
|
||||
struct freedv_advanced {
|
||||
int interleave_frames; // now unused but remains to prevent breaking API for
|
||||
// legacy apps
|
||||
|
||||
// parameters for FREEDV_MODE_FSK_LDPC
|
||||
int M; // 2 or 4 FSK
|
||||
int Rs; // Symbol rate Hz
|
||||
int Fs; // Sample rate Hz
|
||||
int first_tone; // Freq of first tone Hz
|
||||
int tone_spacing; // Spacing between tones Hz
|
||||
char *codename; // LDPC codename, from codes listed in ldpc_codes.c
|
||||
};
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
#include <windows.h>
|
||||
HMODULE hLib;
|
||||
#define GET_HANDLE1 hLib = LoadLibrary("libcodec2.dll")
|
||||
#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll")
|
||||
#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll")
|
||||
#define GET_HANDLE4 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll")
|
||||
#define GET_ADDR(name) (void *)GetProcAddress(hLib, name)
|
||||
#define CLOSE_LIB FreeLibrary(hLib)
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
void * hLib;
|
||||
#define GET_HANDLE1 hLib = dlopen("libcodec2.so", RTLD_LAZY)
|
||||
#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY)
|
||||
#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY)
|
||||
#define GET_HANDLE4 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY)
|
||||
#define GET_ADDR(name) dlsym(hLib, name)
|
||||
#define CLOSE_LIB dlclose(hLib)
|
||||
#endif
|
||||
|
||||
static int requested_mode = -1; // requested mode
|
||||
int freedv_current_mode = -1; // the current running mode
|
||||
static int quisk_freedv_squelch;
|
||||
static int interleave_frames = 1;
|
||||
static int freedv_version = -1;
|
||||
static int quisk_set_tx_bpf = 1;
|
||||
static int handle_index = -1;
|
||||
// need access to these here and in quisk.c
|
||||
int n_speech_sample_rate = 8000;
|
||||
int n_modem_sample_rate = 8000;
|
||||
int n_max_modem_samples = 1000;
|
||||
|
||||
#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000
|
||||
#define MAX_MODEM_SAMPLES 8192 // maximum of n_max_modem_samples for any mode
|
||||
#define MAX_SPEECH_SAMPLES 8192 // maximum of n_speech_samples for any mode
|
||||
static struct _rx_channel{
|
||||
struct freedv * hFreedv;
|
||||
short * demod_in;
|
||||
int rxdata_index;
|
||||
short speech_out[SPEECH_BUF_SIZE]; // output buffer
|
||||
int speech_available; // number of samples in output buffer
|
||||
int playing; // are we currently returning speech samples?
|
||||
} rx_channel[MAX_RECEIVERS] = { 0 }; // safe initialisation to 0
|
||||
|
||||
// freedv_version is the library version number, or
|
||||
// -1 no library was found
|
||||
// -2 a library was found, but freedv_get_version is missing
|
||||
|
||||
// FreeDV API functions:
|
||||
// open, close
|
||||
struct freedv * (*freedv_open)(int mode);
|
||||
struct freedv * (*freedv_open_advanced)(int mode, struct freedv_advanced *adv);
|
||||
void (*freedv_close)(struct freedv *freedv);
|
||||
// Transmit
|
||||
void (*freedv_tx)(struct freedv *freedv, short *, short *);
|
||||
void (*freedv_comptx)(struct freedv *freedv, COMP *, short *);
|
||||
// Receive
|
||||
int (*freedv_nin)(struct freedv *freedv);
|
||||
int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]);
|
||||
int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]);
|
||||
int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]);
|
||||
// Set parameters
|
||||
void (*freedv_set_callback_txt) (struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state);
|
||||
void (*freedv_set_test_frames) (struct freedv *freedv, int test_frames);
|
||||
void (*freedv_set_smooth_symbols) (struct freedv *freedv, int smooth_symbols);
|
||||
void (*freedv_set_squelch_en) (struct freedv *freedv, int squelch_en);
|
||||
void (*freedv_set_snr_squelch_thresh) (struct freedv *freedv, float snr_squelch_thresh);
|
||||
void (*freedv_set_tx_bpf) (struct freedv *freedv, int val);
|
||||
// Get parameters
|
||||
int (*freedv_get_version)(void);
|
||||
void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est);
|
||||
int (*freedv_get_test_frames) (struct freedv *freedv);
|
||||
int (*freedv_get_n_speech_samples) (struct freedv *freedv);
|
||||
int (*freedv_get_n_max_modem_samples) (struct freedv *freedv);
|
||||
int (*freedv_get_n_nom_modem_samples) (struct freedv *freedv);
|
||||
int (*freedv_get_total_bits) (struct freedv *freedv);
|
||||
int (*freedv_get_total_bit_errors) (struct freedv *freedv);
|
||||
int (*freedv_get_modem_sample_rate) (struct freedv *freedv);
|
||||
int (*freedv_get_speech_sample_rate) (struct freedv *freedv);
|
||||
//
|
||||
int (*freedv_get_sync) (struct freedv *freedv);
|
||||
void (*freedv_set_callback_protocol) (struct freedv *freedv, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state);
|
||||
void (*freedv_set_callback_data) (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state);
|
||||
|
||||
//
|
||||
// checkAvxSupport
|
||||
//
|
||||
// This function was copied from freedv, file fdmdv2_main.cpp and tweaked for use here
|
||||
//
|
||||
// Tests the underlying platform for AVX support. 2020 needs AVX support to run
|
||||
// in real-time, and old processors do not offer AVX support
|
||||
//
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
|
||||
#include <cpuid.h>
|
||||
static int checkAvxSupport(void)
|
||||
{
|
||||
|
||||
int isAvxPresent = 0;
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
eax = ebx = ecx = edx = 0;
|
||||
__cpuid(1, eax, ebx, ecx, edx);
|
||||
|
||||
if (ecx & (1<<27) && ecx & (1<<28)) {
|
||||
// CPU supports XSAVE and AVX
|
||||
uint32_t xcr0, xcr0_high;
|
||||
asm("xgetbv" : "=a" (xcr0), "=d" (xcr0_high) : "c" (0));
|
||||
isAvxPresent = (xcr0 & 6) == 6; // AVX state saving enabled?
|
||||
}
|
||||
return isAvxPresent;
|
||||
}
|
||||
#else
|
||||
static int checkAvxSupport(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Called when a new packet can be sent */
|
||||
void my_datatx(void *callback_state, unsigned char *packet, size_t *size) {
|
||||
*size = 0;
|
||||
}
|
||||
|
||||
static void GetAddrs(void)
|
||||
{
|
||||
handle_index = 1;
|
||||
GET_HANDLE1;
|
||||
if ( ! hLib) {
|
||||
handle_index = 2;
|
||||
GET_HANDLE2;
|
||||
if ( ! hLib) {
|
||||
handle_index = 4;
|
||||
GET_HANDLE4;
|
||||
}
|
||||
}
|
||||
if (hLib) {
|
||||
freedv_get_version = GET_ADDR("freedv_get_version");
|
||||
if (freedv_get_version != NULL) {
|
||||
freedv_version = freedv_get_version();
|
||||
if (DEBUG)
|
||||
printf("FreeDV codec2 library %d found, version %d\n", handle_index, freedv_version);
|
||||
}
|
||||
else {
|
||||
freedv_version = -2;
|
||||
if (DEBUG)
|
||||
printf("FreeDV codec2 library %d found, but no freedv_get_version() !!!\n", handle_index);
|
||||
CLOSE_LIB;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
handle_index = -1;
|
||||
if (DEBUG)
|
||||
printf("Could not find the FreeDV codec2 shared library\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// open, close
|
||||
freedv_open = GET_ADDR("freedv_open");
|
||||
freedv_open_advanced = GET_ADDR("freedv_open_advanced");
|
||||
freedv_close = GET_ADDR("freedv_close");
|
||||
// Transmit
|
||||
freedv_tx = GET_ADDR("freedv_tx");
|
||||
freedv_comptx = GET_ADDR("freedv_comptx");
|
||||
// Receive
|
||||
freedv_nin = GET_ADDR("freedv_nin");
|
||||
freedv_rx = GET_ADDR("freedv_rx");
|
||||
freedv_floatrx = GET_ADDR("freedv_floatrx");
|
||||
freedv_comprx = GET_ADDR("freedv_comprx");
|
||||
// Set parameters
|
||||
freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt");
|
||||
freedv_set_callback_protocol = GET_ADDR("freedv_set_callback_protocol");
|
||||
freedv_set_callback_data = GET_ADDR("freedv_set_callback_data");
|
||||
freedv_set_test_frames = GET_ADDR("freedv_set_test_frames");
|
||||
freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols");
|
||||
freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en");
|
||||
freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh");
|
||||
freedv_set_tx_bpf = GET_ADDR("freedv_set_tx_bpf");
|
||||
// Get parameters
|
||||
freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats");
|
||||
freedv_get_test_frames = GET_ADDR("freedv_get_test_frames");
|
||||
freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples");
|
||||
freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples");
|
||||
freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples");
|
||||
freedv_get_total_bits = GET_ADDR("freedv_get_total_bits");
|
||||
freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors");
|
||||
freedv_get_sync = GET_ADDR("freedv_get_sync"); // requires version 11
|
||||
if( freedv_version >= 11)
|
||||
freedv_get_modem_sample_rate = GET_ADDR("freedv_get_modem_sample_rate"); // requires version 11 or later
|
||||
if (freedv_version >= 12)
|
||||
freedv_get_speech_sample_rate = GET_ADDR("freedv_get_speech_sample_rate"); // requires version 12
|
||||
return;
|
||||
}
|
||||
|
||||
static int quisk_freedv_rx(short * sSamples, double * dsamples, int count, int bank) // Called from the sound thread.
|
||||
{ // Input digital modulation is sSamples; decoded voice is dsamples. Each "bank" is a stream of audio.
|
||||
// caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction
|
||||
// output data will be returned at n_speech_sample_rate with corresponding block sizes
|
||||
int i, nout, need, have, sync;
|
||||
int n_speech_samples;
|
||||
struct freedv * hF;
|
||||
struct _rx_channel * pCh;
|
||||
int nCountOut = 1; // In case we have a higher (or lower) value for o/p count
|
||||
|
||||
if (sSamples == NULL) { // shutdown
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if (rx_channel[i].demod_in) {
|
||||
free(rx_channel[i].demod_in);
|
||||
rx_channel[i].demod_in = NULL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bank < 0 || bank >= MAX_RECEIVERS)
|
||||
return 0;
|
||||
hF = rx_channel[bank].hFreedv;
|
||||
if ( ! hF)
|
||||
return 0;
|
||||
pCh = rx_channel + bank;
|
||||
n_speech_samples = freedv_get_n_speech_samples(hF);
|
||||
// if (say) input modem rate is the 2020 mode rate of 8000 sa/sec
|
||||
// and output rate is the corresponding 16000 sa/sec then for an input
|
||||
// block size of N, outbut 2N samples block size.
|
||||
// Apply a multiplier/divisor to 'count' for the output samples count
|
||||
if(n_speech_sample_rate >= n_modem_sample_rate ) {
|
||||
int nRatio = n_speech_sample_rate / n_modem_sample_rate;
|
||||
if( nRatio >= 1 && nRatio <= 6)
|
||||
nCountOut = nRatio * count;
|
||||
}
|
||||
else {
|
||||
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
|
||||
if( nRatio >= 1 && nRatio <= 6)
|
||||
nCountOut = count / nRatio;
|
||||
}
|
||||
nout = 0;
|
||||
need = freedv_nin(hF);
|
||||
for (i = 0; i < count; i++) {
|
||||
pCh->demod_in[pCh->rxdata_index] = sSamples[i];
|
||||
pCh->rxdata_index++;
|
||||
if (pCh->rxdata_index >= need) {
|
||||
if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space
|
||||
have = freedv_rx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
|
||||
if (freedv_version > 10)
|
||||
sync = freedv_get_sync(hF);
|
||||
else
|
||||
freedv_get_modem_stats(hF, &sync, NULL);
|
||||
if (freedv_current_mode == 0) { // mode 1600
|
||||
if (sync) // throw away speech if not in sync
|
||||
pCh->speech_available += have;
|
||||
}
|
||||
else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) {
|
||||
pCh->speech_available += have; // keep speech if there is space
|
||||
}
|
||||
else {
|
||||
if (DEBUG) printf("Close to maximum in speech output buffer\n");
|
||||
}
|
||||
}
|
||||
else { // no space in buffer
|
||||
if (DEBUG) printf("Overflow in speech output buffer\n");
|
||||
}
|
||||
pCh->rxdata_index = 0;
|
||||
need = freedv_nin(hF);
|
||||
}
|
||||
}
|
||||
if ( ! pCh->playing) {
|
||||
if (pCh->speech_available >= 2 * n_speech_samples) {
|
||||
pCh->playing = 1;
|
||||
}
|
||||
else { // return zero samples
|
||||
for (i = 0; i < nCountOut; i++)
|
||||
dsamples[i] = 0;
|
||||
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
|
||||
return nCountOut;
|
||||
}
|
||||
}
|
||||
for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++)
|
||||
dsamples[nout] = pCh->speech_out[nout] * ((double)CLIP32 / CLIP16 * 0.99);
|
||||
if (nout) {
|
||||
pCh->speech_available -= nout;
|
||||
memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
|
||||
}
|
||||
if ( ! pCh->speech_available) {
|
||||
pCh->playing = 0;
|
||||
while (nout < nCountOut)
|
||||
dsamples[nout++] = 0;
|
||||
}
|
||||
//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
|
||||
return nout;
|
||||
}
|
||||
|
||||
static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count, int is_real) // Called from the sound thread.
|
||||
{ // Input voice samples are dsamples; output digital modulation is cSamples.
|
||||
int i, nout;
|
||||
int n_speech_samples;
|
||||
int n_nom_modem_samples;
|
||||
static COMP * mod_out = NULL;
|
||||
static short * speech_in = NULL;
|
||||
static int speech_index=0, mod_index=0;
|
||||
// g8kbb - add for 800XA
|
||||
static short *real_mod_out = NULL;
|
||||
|
||||
if (dsamples == NULL) { // shutdown
|
||||
return 0; // nothing to do
|
||||
}
|
||||
if ( ! rx_channel[0].hFreedv)
|
||||
return 0;
|
||||
n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv);
|
||||
n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv);
|
||||
// find if need to output at faster rate
|
||||
int nRatio = n_modem_sample_rate / n_speech_sample_rate;
|
||||
if( nRatio < 1 || nRatio > 6 )
|
||||
nRatio = 1;
|
||||
if (mod_out == NULL) { // initialize
|
||||
mod_out = (COMP *)malloc(sizeof(COMP) * MAX_MODEM_SAMPLES);
|
||||
memset(mod_out, 0, sizeof(COMP) * MAX_MODEM_SAMPLES);
|
||||
speech_in = (short*)malloc(sizeof(short) * MAX_SPEECH_SAMPLES);
|
||||
speech_index=0;
|
||||
mod_index=0;
|
||||
real_mod_out = (short *)malloc(sizeof(short) * MAX_MODEM_SAMPLES);
|
||||
memset(real_mod_out, 0, sizeof(short) * MAX_MODEM_SAMPLES);
|
||||
//if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples );
|
||||
}
|
||||
nout = 0;
|
||||
for (i = 0; i < count; i++) {
|
||||
speech_in[speech_index++] = (short)dsamples[i];
|
||||
if (speech_index >= n_speech_samples) {
|
||||
// Calculate a new block, but first write out the rest of the old block
|
||||
// 800xa uses real not complex
|
||||
if( is_real ) {
|
||||
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
||||
cSamples[nout++] = real_mod_out[mod_index];
|
||||
freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in);
|
||||
}
|
||||
else {
|
||||
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
||||
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
|
||||
freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in);
|
||||
}
|
||||
//if( DEBUG ) printf("Block processed, speech index=%d, n_speech_samples=%d, count=%d\n", speech_index, n_speech_samples, count );
|
||||
mod_index = 0;
|
||||
speech_index = 0;
|
||||
}
|
||||
else { // write out samples
|
||||
for( int j=0; j<nRatio; j++) {
|
||||
if (mod_index < n_nom_modem_samples ) {
|
||||
if( is_real ) {
|
||||
cSamples[nout++] = real_mod_out[mod_index];
|
||||
}
|
||||
else {
|
||||
cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
|
||||
}
|
||||
mod_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rxMode == FDV_L)
|
||||
for (i = 0; i < nout; i++)
|
||||
cSamples[i] = conj(cSamples[i]);
|
||||
return nout;
|
||||
}
|
||||
|
||||
#define TX_MSG_SIZE 80
|
||||
static char quisk_tx_msg[TX_MSG_SIZE];
|
||||
|
||||
static char get_next_tx_char(void * callback_state)
|
||||
{
|
||||
char c;
|
||||
static int index = 0;
|
||||
|
||||
c = quisk_tx_msg[index++];
|
||||
if (index >= TX_MSG_SIZE)
|
||||
index = 0;
|
||||
if ( ! c) {
|
||||
index = 0;
|
||||
c = quisk_tx_msg[index++];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
#define RX_MSG_SIZE 80
|
||||
static char quisk_rx_msg[RX_MSG_SIZE + 1];
|
||||
|
||||
static void put_next_rx_char(void * callback_state, char ch)
|
||||
{
|
||||
if (ch == '\n' || ch == '\r')
|
||||
ch = ' ';
|
||||
if (ch < 32 || ch > 126) // printable characters
|
||||
return;
|
||||
if (strlen(quisk_rx_msg) < RX_MSG_SIZE)
|
||||
strncat(quisk_rx_msg, &ch, 1);
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
PyObject * txt;
|
||||
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
txt = PyString_FromString(quisk_rx_msg);
|
||||
quisk_rx_msg[0] = 0;
|
||||
return txt;
|
||||
}
|
||||
|
||||
static void CloseFreedv(void) // Called from the GUI thread or sound thread
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if (rx_channel[i].hFreedv) {
|
||||
freedv_close(rx_channel[i].hFreedv);
|
||||
rx_channel[i].hFreedv = NULL;
|
||||
}
|
||||
}
|
||||
quisk_freedv_rx(NULL, NULL, 0, 0);
|
||||
quisk_freedv_tx(NULL, NULL, 0, 0);
|
||||
freedv_current_mode = -1;
|
||||
}
|
||||
|
||||
static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
||||
{
|
||||
int i;
|
||||
struct freedv * hF;
|
||||
|
||||
if ( ! hLib)
|
||||
GetAddrs(); // Get the entry points for funtions in the codec2 library
|
||||
if (DEBUG) {
|
||||
switch (handle_index) {
|
||||
case 1:
|
||||
printf("freedv_open: system codec2 library found, version %d\n", freedv_version);
|
||||
break;
|
||||
case 2:
|
||||
printf("freedv_open: codec2 library freedvpkg/libcodec2.dll|so found, version %d\n", freedv_version);
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
printf("freedv_open: codec2 library freedvpkg/libcodec2_64 found, version %d\n", freedv_version);
|
||||
break;
|
||||
default:
|
||||
printf("freedv_open: Could not find the FreeDV codec2 shared library\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (freedv_version < 10) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because version is less than 10\n");
|
||||
return 0; // failure
|
||||
}
|
||||
if (requested_mode == FREEDV_MODE_2020) {
|
||||
if (checkAvxSupport() == 0) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because mode 2020 requires Avx support\n");
|
||||
return 0; // failure
|
||||
}
|
||||
if (handle_index >= 3) { // Quisk provided codec2
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
if (DEBUG)
|
||||
printf("freedv_open: Failure because mode 2020 requires a system installation of codec2\n");
|
||||
return 0; // failure
|
||||
}
|
||||
}
|
||||
|
||||
if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) {
|
||||
struct freedv_advanced adv;
|
||||
memset(&adv, 0, sizeof(adv));
|
||||
adv.interleave_frames = interleave_frames;
|
||||
hF = freedv_open_advanced(requested_mode, &adv);
|
||||
}
|
||||
else {
|
||||
hF = freedv_open(requested_mode);
|
||||
}
|
||||
if (hF == NULL) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
return 0; // failure
|
||||
}
|
||||
rx_channel[0].hFreedv = hF;
|
||||
quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx);
|
||||
if (quisk_tx_msg[0])
|
||||
freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL);
|
||||
else
|
||||
freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL);
|
||||
if (freedv_set_callback_protocol)
|
||||
freedv_set_callback_protocol(hF, NULL, NULL, NULL);
|
||||
if (freedv_set_callback_data)
|
||||
freedv_set_callback_data(hF, NULL, my_datatx, NULL);
|
||||
freedv_set_squelch_en(hF, quisk_freedv_squelch);
|
||||
if (freedv_set_tx_bpf)
|
||||
freedv_set_tx_bpf(hF, quisk_set_tx_bpf);
|
||||
n_max_modem_samples = freedv_get_n_max_modem_samples(hF);
|
||||
n_speech_sample_rate = 8000;
|
||||
if (freedv_version >= 12 && freedv_get_speech_sample_rate)
|
||||
n_speech_sample_rate = freedv_get_speech_sample_rate(hF);
|
||||
n_modem_sample_rate = 8000;
|
||||
if (freedv_version >= 11 && freedv_get_modem_sample_rate)
|
||||
n_modem_sample_rate = freedv_get_modem_sample_rate(hF);
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
rx_channel[i].rxdata_index = 0;
|
||||
rx_channel[i].speech_available = 0;
|
||||
rx_channel[i].playing = 0;
|
||||
if (rx_channel[i].demod_in)
|
||||
free(rx_channel[i].demod_in);
|
||||
rx_channel[i].demod_in = (short *)malloc(sizeof(short) * n_max_modem_samples);
|
||||
if( rx_channel[i].demod_in == NULL) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
return 0; // failure
|
||||
}
|
||||
if (i > 0) {
|
||||
rx_channel[i].hFreedv = freedv_open(requested_mode);
|
||||
if (rx_channel[i].hFreedv)
|
||||
freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch);
|
||||
}
|
||||
}
|
||||
if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv));
|
||||
if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv));
|
||||
if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples);
|
||||
if (DEBUG) {
|
||||
if (freedv_version >= 11) printf("modem_sample_rate %d\n", n_modem_sample_rate);
|
||||
if (freedv_version >= 12) printf("speech_sample_rate %d\n", n_speech_sample_rate);
|
||||
}
|
||||
freedv_current_mode = requested_mode;
|
||||
return 1; // success
|
||||
}
|
||||
|
||||
void quisk_check_freedv_mode(void)
|
||||
{ // see if we need to change the mode
|
||||
if (requested_mode == freedv_current_mode)
|
||||
return;
|
||||
if (DEBUG) printf("Change in mode to %d\n", requested_mode);
|
||||
CloseFreedv();
|
||||
if (requested_mode >= 0)
|
||||
OpenFreedv();
|
||||
else
|
||||
requested_mode = -1;
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_open(PyObject * self, PyObject * args) // Called from the GUI thread before freedv is open
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
return PyInt_FromLong(OpenFreedv());
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_close(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
requested_mode = -1; // request close
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds) // Called from the GUI thread.
|
||||
{ // Call with keyword arguments ONLY to change parameters. Call before quisk_freedv_open() to set an initial mode.
|
||||
int mode=-1; // Call again to change the mode.
|
||||
int bpf=-1;
|
||||
char * ptMsg=NULL;
|
||||
static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", "interleave_frames", "set_tx_bpf", NULL} ;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf))
|
||||
return NULL;
|
||||
if (ptMsg)
|
||||
strMcpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE);
|
||||
if (bpf != -1) {
|
||||
quisk_set_tx_bpf = bpf;
|
||||
if (freedv_set_tx_bpf && rx_channel[0].hFreedv)
|
||||
freedv_set_tx_bpf(rx_channel[0].hFreedv, quisk_set_tx_bpf);
|
||||
}
|
||||
if (mode == -1) { // mode was not entered
|
||||
;
|
||||
}
|
||||
else if (mode == FREEDV_MODE_700E && freedv_version >= 14) {
|
||||
requested_mode = mode;
|
||||
}
|
||||
else if (mode == FREEDV_MODE_2020) {
|
||||
if (checkAvxSupport() && handle_index <= 2 && freedv_version >= 13)
|
||||
requested_mode = mode;
|
||||
}
|
||||
else if (freedv_version == 10) {
|
||||
if (mode == 0)
|
||||
requested_mode = mode;
|
||||
}
|
||||
else if (freedv_version == 11) {
|
||||
if (mode <= 2)
|
||||
requested_mode = mode;
|
||||
}
|
||||
else {
|
||||
requested_mode = mode;
|
||||
}
|
||||
return PyInt_FromLong(requested_mode); // Return the mode
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
float snr_est = 0.0;
|
||||
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
if (rx_channel[0].hFreedv)
|
||||
freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est);
|
||||
return PyFloat_FromDouble(snr_est);
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, ""))
|
||||
return NULL;
|
||||
if ( ! hLib)
|
||||
GetAddrs(); // Get the entry points for funtions in the codec2 library
|
||||
return PyInt_FromLong(freedv_version);
|
||||
}
|
||||
|
||||
PyObject * quisk_freedv_set_squelch_en(PyObject * self, PyObject * args) // Called from the GUI thread.
|
||||
{
|
||||
int i,state;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", &state))
|
||||
return NULL;
|
||||
quisk_freedv_squelch = state;
|
||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||
if( rx_channel[i].hFreedv )
|
||||
freedv_set_squelch_en(rx_channel[i].hFreedv, state);
|
||||
}
|
||||
return PyInt_FromLong(state);
|
||||
}
|
||||
|
||||
|
||||
// This file is no longer used.
|
||||
|
||||
@@ -1,24 +1 @@
|
||||
extern int freedv_current_mode; // the current FreeDV running mode
|
||||
|
||||
// available speech modes
|
||||
#define FREEDV_MODE_1600 0
|
||||
#define FREEDV_MODE_2400A 3
|
||||
#define FREEDV_MODE_2400B 4
|
||||
#define FREEDV_MODE_800XA 5
|
||||
#define FREEDV_MODE_700C 6
|
||||
#define FREEDV_MODE_700D 7
|
||||
#define FREEDV_MODE_700E 13
|
||||
#define FREEDV_MODE_2020 8
|
||||
#define FREEDV_MODE_2020B 16
|
||||
|
||||
// available data modes
|
||||
#define FREEDV_MODE_FSK_LDPC 9
|
||||
#define FREEDV_MODE_DATAC1 10
|
||||
#define FREEDV_MODE_DATAC3 12
|
||||
#define FREEDV_MODE_DATAC0 14
|
||||
#define FREEDV_MODE_DATAC4 18
|
||||
#define FREEDV_MODE_DATAC13 19
|
||||
|
||||
// Sample rates used
|
||||
#define FREEDV_FS_8000 8000
|
||||
#define FREEDV_FS_16000 16000
|
||||
// This header file is no longer used.
|
||||
|
||||
+1
-129
@@ -1,129 +1 @@
|
||||
|
||||
Note: The directory freedvpkg no longer contains source files. It only contains copies of
|
||||
the codec2 libraries for use by Quisk. If any other files are present, delete them.
|
||||
|
||||
FreeDV and Directory freedvpkg
|
||||
==============================
|
||||
|
||||
FreeDV is the combination of the codec2 codec and the fdmdv modem. It provides digital voice
|
||||
suitable for HF and VHF transmission. Quisk has native (built-in) support for
|
||||
FreeDV. Just push the FDV mode and talk. This freedvpkg directory contains copies of the
|
||||
codec2 libraries for use by Quisk.
|
||||
|
||||
You can also use the separate FreeDV program available at freedv.org, and the Quisk DGT-U mode
|
||||
which attaches to external digital programs. The setup is identical to fldigi and other external
|
||||
digital programs.
|
||||
|
||||
Quisk will add the FDV button if specified on the Config/radio/Controls tab.
|
||||
If there is a problem with the freedv module, the button will be grayed out.
|
||||
|
||||
The freedv module requires the codec2 library. This library is included for Windows and for
|
||||
Ubuntu 18.04 LTS 32-bit and 64-bit. For other systems (such as ARM) you will need to build another
|
||||
codec2. Just try the FDV mode and see if it works. It should always work on Windows, and may work
|
||||
on Linux. If the FDV button is grayed out, you need a different codec2 than the one included. Make
|
||||
a new codec2 library, and copy it to freedvpkg or to the correct system directory.
|
||||
|
||||
2020 mode
|
||||
=========
|
||||
You can now use 2020 mode with Quisk. Note however that 2020 mode requires a processor with AVX.
|
||||
Also note a Raspberry PI (model 4B) does not have enough processing power even if neon is used
|
||||
and it is overclocked.
|
||||
|
||||
There are two parts to this:
|
||||
1. build LPCNet
|
||||
2. rebuild codec2
|
||||
The details are found here: https://github.com/drowe67/codec2#freedv-2020-support-building-with-lpcnet
|
||||
|
||||
Both liblpcnetfreedv and libcodec2 are needed. If you are running under windows, the resultant
|
||||
liblpcnetfreedv.dll file needs to be placed in the quisk folder. Libcodec2 may be placed in the
|
||||
quisk freedvpkg folder. Or install the DLL's in the correct system folders.
|
||||
|
||||
Search Order
|
||||
============
|
||||
|
||||
Quisk will search for a valid codec2 library in this order on Windows:
|
||||
1. The system codec2 library installed outside of Quisk by another program.
|
||||
2. freedvpkg/libcodec2.dll. Not included. You can copy your own codec2 here but a system-wide installation is better.
|
||||
3. freedvpkg/libcodec2_32.dll. The 32-bit codec2 shipped with Quisk.
|
||||
4. freedvpkg/libcodec2_64.dll. The 64-bit codec2 shipped with Quisk.
|
||||
|
||||
Quisk will search for a valid codec2 library in this order on Linux:
|
||||
1. The system codec2 library installed outside of Quisk by another program.
|
||||
2. freedvpkg/libcodec2.so. Not included. You can copy your own codec2 here but a system-wide installation is better.
|
||||
3. freedvpkg/libcodec2_32.so. The 32-bit codec2 shipped with Quisk.
|
||||
4. freedvpkg/libcodec2_64.so. The 64-bit codec2 shipped with Quisk.
|
||||
|
||||
How to Build a New codec2
|
||||
=========================
|
||||
|
||||
The source for codec2 is available on github. Or google for other sources or perhaps a pre-built library.
|
||||
If you need to compile codec2 from source, first change to a suitable directory (not the Quisk directory)
|
||||
and download the source with git:
|
||||
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
|
||||
Then build codec2 using the directions found at https://github.com/drowe67/codec2/blob/master/INSTALL.
|
||||
The directions given below are current as of June 2020, but check for changes.
|
||||
Then copy the codec2 library to the freedvpkg directory under Quisk.
|
||||
|
||||
Build a New codec2 on Linux
|
||||
===========================
|
||||
Create the codec2 shared library. This assumes a 64-bit linux. Change the directory
|
||||
name from build_linux64 to build_linux32 for 32-bit linux. Note the "../".
|
||||
|
||||
cd codec2
|
||||
mkdir build_linux64
|
||||
cd build_linux64
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../
|
||||
make
|
||||
cd src
|
||||
cp libcodec2.so my-quisk-directory/freedvpkg
|
||||
|
||||
Build a New 32-bit codec2 on 64-bit Linux
|
||||
=========================================
|
||||
Make sure package libc6-dev-i386 is installed.
|
||||
Create the codec2 shared library. Note the "../".
|
||||
|
||||
cd codec2
|
||||
mkdir build_linux32
|
||||
cd build_linux32
|
||||
export CFLAGS=-m32
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../
|
||||
make
|
||||
cd src
|
||||
cp libcodec2.so my-quisk-directory/freedvpkg
|
||||
|
||||
Build a New codec2 on Windows
|
||||
=============================
|
||||
For Windows you need to install MinGW-w64, MSYS2, and g++. Use the mingw shell. The Speex libraries
|
||||
are not needed by codec2, but are required for the Unit Test modules. To build the Unit Test modules,
|
||||
you need to install Speex and add -DSPEEXDSP_INCLUDE_DIR=../speex/include/speex -DSPEEXDSP_LIBRARY=../speex/bin.
|
||||
|
||||
# Use mingw32.exe 32-bit shell:
|
||||
cd codec2
|
||||
mkdir build_win32
|
||||
cd build_win32
|
||||
cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF ../
|
||||
make codec2
|
||||
cd src
|
||||
cp libcodec2.dll my-quisk-directory/freedvpkg
|
||||
|
||||
# Use mingw64.exe 64-bit shell:
|
||||
cd codec2
|
||||
mkdir build_win64
|
||||
cd build_win64
|
||||
cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF -DCMAKE_SYSTEM_PROCESSOR=x86_64 ../
|
||||
make codec2
|
||||
cd src
|
||||
cp libcodec2.dll my-quisk-directory/freedvpkg
|
||||
|
||||
Testing
|
||||
=======
|
||||
You can just start Quisk and see if the FDV button is not grayed out, and FDV works. Or you can
|
||||
test the import of freedv and look for error messages.
|
||||
|
||||
cd my-quisk-directory
|
||||
python # Use the correct Python 2 or 3 in 32 or 64 bit
|
||||
import _quisk
|
||||
_quisk.freedv_get_version() # This should return 13 or higher for a recent codec2
|
||||
|
||||
This directory is no longer used by FreeDV. Please see the documentation.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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()
|
||||
+43
-15
@@ -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,9 +652,17 @@ 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]
|
||||
@@ -653,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
|
||||
@@ -876,6 +902,8 @@ class Hardware(BaseHardware):
|
||||
QS.set_hermeslite_writepointer(0)
|
||||
return False
|
||||
def WriteQueue(self, wait=False):
|
||||
if not self.is_HermesLite2:
|
||||
return False
|
||||
self._wait_queue()
|
||||
# Send next write
|
||||
self.old_writequeue = self.pc2hermeslitewritequeue[:]
|
||||
|
||||
+36
-3
@@ -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
|
||||
|
||||
+617
-161
@@ -8,7 +8,6 @@
|
||||
#include <sys/types.h>
|
||||
#include "microphone.h"
|
||||
#include "filter.h"
|
||||
#include "freedv.h"
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
#include <winsock2.h>
|
||||
@@ -245,11 +244,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;
|
||||
@@ -617,7 +614,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
|
||||
@@ -626,140 +623,6 @@ static int tx_filter_digital(complex double * filtered, int count)
|
||||
return count;
|
||||
}
|
||||
|
||||
static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
||||
{ // Input samples are creal(filtered), output is filtered.
|
||||
// This filter is used for digital voice.
|
||||
// Changes by Dave Roberts, G8KBB, for additional modes June, 2020.
|
||||
// Modified by N2ADR January, 2024.
|
||||
int i;
|
||||
// The input sample rate is mic_sample_rate, either 8000 or 48000 sps.
|
||||
// The FreeDV codec requires an input speech_sample_rate of either 8000 or 16000 sps depending on the mode. Input is always real.
|
||||
// The FreeDV codec will output samples at modem_sample_rate of 8000 or 48000 sps depending on the mode, and output may be real or complex.
|
||||
double dtmp, magn, dsample;
|
||||
int modem_sample_rate, speech_sample_rate, use_filter, is_real;
|
||||
static int samples_size = 0;
|
||||
static int do_init = 1;
|
||||
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
|
||||
static double time_long, time_short;
|
||||
static double inMax=0.3;
|
||||
static double * dsamples = NULL;
|
||||
static struct quisk_cFilter filter1, filter2;
|
||||
static struct quisk_dFilter filtDecim48to16, filtDecim16to8;;
|
||||
static struct quisk_cFilter cfiltInterp8to48;
|
||||
|
||||
if (do_init) { // initialization
|
||||
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
|
||||
do_init = 0;
|
||||
quisk_filt_cInit(&filter1, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // at 48000 sps pass 3000 stop 4000 times two plus 3000
|
||||
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // at 8000 sps pass 1500 stop 1800 times two plus 1500
|
||||
quisk_filt_cInit(&cfiltInterp8to48, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
||||
quisk_filt_dInit(&filtDecim48to16, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double)); // pass 6000 stop 8000
|
||||
quisk_filt_dInit(&filtDecim16to8, quiskFilt16dec8Coefs, sizeof(quiskFilt16dec8Coefs)/sizeof(double)); // pass 3000 stop 4000
|
||||
Ymax = pow(10.0, - 1 / 20.0); // maximum y
|
||||
Xmax = pow(10.0, 3 / 20.0); // x where slope is zero; for x > Xmax, y == Ymax
|
||||
Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression
|
||||
//printf ("Xmin %f\n", Xmin);
|
||||
aaa = 1.0 / (2.0 * (Xmin - Xmax)); // quadratic
|
||||
bbb = -2.0 * aaa * Xmax;
|
||||
ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax;
|
||||
}
|
||||
switch (freedv_current_mode) { // n_modem_sample_rate and n_speech_sample_rate may not be valid
|
||||
case FREEDV_MODE_2400A:
|
||||
case FREEDV_MODE_2400B:
|
||||
use_filter = 1; // center frequency 3000
|
||||
is_real = 1;
|
||||
modem_sample_rate = 48000;
|
||||
speech_sample_rate = 8000;
|
||||
break;
|
||||
case FREEDV_MODE_2020:
|
||||
use_filter = 0;
|
||||
is_real = 0;
|
||||
modem_sample_rate = 8000;
|
||||
speech_sample_rate = 16000;
|
||||
break;
|
||||
case FREEDV_MODE_800XA:
|
||||
use_filter = 2; // center frequency 1500
|
||||
is_real = 1;
|
||||
modem_sample_rate = 8000;
|
||||
speech_sample_rate = 8000;
|
||||
break;
|
||||
default:
|
||||
use_filter = 0;
|
||||
is_real = 0;
|
||||
modem_sample_rate = 8000;
|
||||
speech_sample_rate = 8000;
|
||||
break;
|
||||
}
|
||||
if ( ! filtered) { // rxMode changed
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter1, 3000.0 / 48000.0, rxMode != FDV_L);
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1500.0 / 8000.0, rxMode != FDV_L);
|
||||
return 0;
|
||||
}
|
||||
// check size of dsamples[] buffer
|
||||
if (count > samples_size) {
|
||||
samples_size = count * 2;
|
||||
if (dsamples)
|
||||
free(dsamples);
|
||||
dsamples = (double *)malloc(samples_size * sizeof(double));
|
||||
}
|
||||
// copy to dsamples[]
|
||||
for (i = 0; i < count; i++)
|
||||
dsamples[i] = creal(filtered[i]) / CLIP16; // normalize to 1.0000
|
||||
// Decimate to 8000 Hz or to 16000 depending on mode (and hence setting of sample rate)
|
||||
if (quisk_sound_state.mic_sample_rate == 48000) {
|
||||
switch (speech_sample_rate) {
|
||||
case 16000:
|
||||
count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3);
|
||||
break;
|
||||
case 8000:
|
||||
count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3);
|
||||
count = quisk_dDecimate(dsamples, count, &filtDecim16to8, 2);
|
||||
break;
|
||||
default:
|
||||
QuiskPrintf("Failure to convert speech input rate in tx_filter_freedv\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (quisk_sound_state.mic_sample_rate == 8000 && speech_sample_rate != 8000) {
|
||||
QuiskPrintf("Failure to convert input rate in tx_filter_freedv\n");
|
||||
}
|
||||
// Measure average peak input audio level and limit
|
||||
dtmp = 1.0 / speech_sample_rate; // sample time
|
||||
time_long = 1.0 - exp(- dtmp / 3.000);
|
||||
time_short = 1.0 - exp(- dtmp / 0.005);
|
||||
for (i = 0; i < count; i++) {
|
||||
dsample = dsamples[i];
|
||||
magn = fabs(dsample);
|
||||
if (magn > inMax)
|
||||
inMax = inMax * (1 - time_short) + time_short * magn;
|
||||
else if(magn > mic_agc_level)
|
||||
inMax = inMax * (1 - time_long) + time_long * magn;
|
||||
else
|
||||
inMax = inMax * (1 - time_long) + time_long * mic_agc_level;
|
||||
dsample = dsample / inMax * Xmin * 0.7;
|
||||
magn = fabs(dsample);
|
||||
if (magn < Xmin)
|
||||
dsamples[i] = dsample;
|
||||
else if (magn > Xmax)
|
||||
dsamples[i] = copysign(Ymax, dsample);
|
||||
else
|
||||
dsamples[i] = copysign(aaa * magn * magn + bbb * magn + ccc, dsample);
|
||||
dsamples[i] = dsamples[i] * CLIP16;
|
||||
}
|
||||
//QuiskWavWriteD(&hWav, dsamples, count);
|
||||
if (encode && pt_quisk_freedv_tx) // Encode audio into digital modulation
|
||||
count = (* pt_quisk_freedv_tx)(filtered, dsamples, count, is_real);
|
||||
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
|
||||
if (use_filter == 1) // convert float samples to complex with an analytic filter
|
||||
count = quisk_cCDecimate(filtered, count, &filter1, 1);
|
||||
else if (use_filter == 2)
|
||||
count = quisk_cCDecimate(filtered, count, &filter2, 1);
|
||||
// Interpolate up to 48000
|
||||
if (MIC_OUT_RATE != modem_sample_rate )
|
||||
count = quisk_cInterpolate(filtered, count, &cfiltInterp8to48, MIC_OUT_RATE / modem_sample_rate);
|
||||
return count;
|
||||
}
|
||||
|
||||
PyObject * quisk_get_tx_filter(PyObject * self, PyObject * args)
|
||||
{ // return the TX filter response to display on the graph
|
||||
// This is for debugging. Change quisk.py to call QS.get_tx_filter() instead
|
||||
@@ -896,6 +759,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]);
|
||||
@@ -911,6 +776,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;
|
||||
@@ -918,6 +784,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) {
|
||||
@@ -930,6 +797,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);
|
||||
@@ -973,15 +841,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;
|
||||
@@ -1030,15 +916,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;
|
||||
}
|
||||
@@ -1104,12 +1008,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;
|
||||
@@ -1321,6 +1260,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;
|
||||
@@ -1328,11 +1269,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;
|
||||
}
|
||||
@@ -1423,11 +1359,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;
|
||||
@@ -1464,14 +1395,16 @@ PyObject * quisk_get_tx_audio(PyObject * self, PyObject * args)
|
||||
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
|
||||
@@ -1595,7 +1528,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);
|
||||
}
|
||||
|
||||
@@ -1641,3 +1573,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# This is the hardware control file for my shack.
|
||||
# It is for the Hermes-Lite2 5 watt output which uses only the antenna tuner.
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
# It is for the Hermes-Lite2 RF1 output and Alt Rx input.
|
||||
|
||||
from hermes.quisk_hardware import Hardware as BaseHw
|
||||
from n2adr import station_hardware
|
||||
@@ -17,7 +14,7 @@ class Hardware(BaseHw):
|
||||
self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box
|
||||
self.v2filter = station_hardware.FilterBoxV2(app, conf) # Control V2 filter box
|
||||
def open(self):
|
||||
if False:
|
||||
if False: # Start the station control GUI
|
||||
from n2adr.station_hardware import StationControlGUI
|
||||
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
|
||||
self.GUI.Show()
|
||||
@@ -29,12 +26,11 @@ class Hardware(BaseHw):
|
||||
return BaseHw.close(self)
|
||||
def ChangeFilterFrequency(self, tx_freq):
|
||||
if tx_freq and tx_freq > 0:
|
||||
self.v2filter.SetTxFreq(tx_freq)
|
||||
if self.GUI:
|
||||
self.GUI.SetTxFreq(tx_freq)
|
||||
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
|
||||
else:
|
||||
self.anttuner.SetTxFreq(tx_freq)
|
||||
self.v2filter.SetTxFreq(tx_freq)
|
||||
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
|
||||
self.ChangeFilterFrequency(tx_freq)
|
||||
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
|
||||
@@ -42,8 +38,6 @@ class Hardware(BaseHw):
|
||||
# band is a string: "60", "40", "WWV", etc.
|
||||
ret = BaseHw.ChangeBand(self, band)
|
||||
self.anttuner.ChangeBand(band)
|
||||
#self.lpfilter.ChangeBand(band)
|
||||
#self.hpfilter.ChangeBand(band)
|
||||
self.v2filter.ChangeBand(band)
|
||||
self.CorrectSmeter()
|
||||
return ret
|
||||
|
||||
+4
-50
@@ -1,56 +1,10 @@
|
||||
# Please do not change this widgets module for Quisk. Instead copy
|
||||
# it to your own quisk_widgets.py and make changes there.
|
||||
#
|
||||
# This module is used to add extra widgets to the QUISK screen.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import wx, time
|
||||
import _quisk as QS
|
||||
|
||||
class BottomWidgets: # Add extra widgets to the bottom of the screen
|
||||
from hermes.quisk_widgets import BottomWidgets as BaseWidgets
|
||||
|
||||
class BottomWidgets(BaseWidgets): # Add extra widgets to the bottom of the screen
|
||||
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
|
||||
#self.config = conf
|
||||
#self.hardware = hardware
|
||||
#self.application = app
|
||||
#start_row = app.widget_row # The first available row
|
||||
#start_col = app.button_start_col # The start of the button columns
|
||||
#b = app.QuiskCycleCheckbutton(frame, self.OnAntTuner, ('Antenna', 'Ant 0', 'Ant 1'))
|
||||
#bw, bh = b.GetMinSize()
|
||||
#gbs.Add(b, (start_row, start_col), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L+')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 2), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L-')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 4), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C+')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 6), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C-')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 8), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'Save')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 10), (1, 2), flag=wx.EXPAND)
|
||||
#self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
|
||||
#gbs.Add(self.swr_label, (start_row, start_col + 2), (1, 10), flag=wx.EXPAND)
|
||||
#b = app.QuiskCheckbutton(frame, None, text='')
|
||||
#gbs.Add(b, (start_row, start_col + 25), (1, 2), flag=wx.EXPAND)
|
||||
# Example of a horizontal slider:
|
||||
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
|
||||
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
|
||||
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
|
||||
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
|
||||
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
|
||||
# def OnPreamp(self, event):
|
||||
# print event.GetPosition()
|
||||
self.num_rows_added = 0
|
||||
#def OnAntTuner(self, event):
|
||||
# btn = event.GetEventObject()
|
||||
# text = btn.GetLabel()
|
||||
# self.hardware.OnAntTuner(text)
|
||||
#def UpdateText(self, text):
|
||||
# self.swr_label.SetLabel(text)
|
||||
BaseWidgets.__init__(self, app, hardware, conf, frame, gbs, vertBox)
|
||||
|
||||
+85
-123
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -151,7 +151,6 @@ static int waterfall_scroll_mode = 1; // draw the first lines multiple times
|
||||
static int quisk_use_sidetone; // is there a sidetone volume control?
|
||||
static int hl2_txbuf_errors; // errors in the Hermes-Lite2 Tx buffer
|
||||
static int hl2_txbuf_state; // state machine for errors in the Hermes-Lite2 Tx buffer
|
||||
static int freedv_monitor; // pass the Freedv audio to the speakers instead of to the demodulator
|
||||
static int softrock_correct_freq; // gain and phase corrections for SoftRock
|
||||
static double softrock_correct_gain;
|
||||
static double softrock_correct_phase;
|
||||
@@ -219,14 +218,17 @@ static unsigned char quisk_hermes_to_pc[5 * 4]; // data received from the Herme
|
||||
static unsigned char quisk_hermeslite_response[5]; // response from Hermes-Lite commands
|
||||
unsigned int quisk_hermes_code_version = -1; // code version returned by the Hermes hardware
|
||||
unsigned int quisk_hermes_board_id = -1; // board ID returned by the Hermes hardware
|
||||
static double hermes_temperature; // average temperature
|
||||
static double hermes_fwd_power; // average forward power
|
||||
static double hermes_rev_power; // average reverse power
|
||||
static double hermes_fwd_peak; // peak forward power
|
||||
static double hermes_rev_peak; // peak reverse power
|
||||
static double hermes_pa_current; // average power amp current
|
||||
static int hermes_count_temperature; // number of temperature samples
|
||||
static int hermes_count_current; // number of current samples
|
||||
static double hpsdr_AIN1_avg; // average forward power
|
||||
static double hpsdr_AIN1_peak; // peak forward power
|
||||
static double hpsdr_AIN2_avg; // average reverse power
|
||||
static double hpsdr_AIN2_peak; // peak reverse power
|
||||
static double hpsdr_AIN3; // average power amp current
|
||||
static double hpsdr_AIN4;
|
||||
static double hpsdr_AIN5; // average temperature
|
||||
static double hpsdr_AIN6;
|
||||
static int hpsdr_AIN3_count; // number of current samples
|
||||
static int hpsdr_AIN4_count; // number of current samples
|
||||
static int hpsdr_AIN5_count; // number of current samples
|
||||
static int hardware_ptt; // hardware PTT switch
|
||||
|
||||
int quisk_hardware_cwkey; // hardware CW key from UDP or USB
|
||||
@@ -569,15 +571,10 @@ void QuiskWavClose(struct QuiskWav * hWav)
|
||||
}
|
||||
#endif
|
||||
|
||||
// These are used for digital voice codecs
|
||||
ty_dvoice_codec_rx pt_quisk_freedv_rx;
|
||||
ty_dvoice_codec_tx pt_quisk_freedv_tx;
|
||||
|
||||
void quisk_dvoice_freedv(ty_dvoice_codec_rx rx, ty_dvoice_codec_tx tx)
|
||||
void quisk_dvoice_freedv(void) // Not used.
|
||||
{
|
||||
pt_quisk_freedv_rx = rx;
|
||||
pt_quisk_freedv_tx = tx;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int fFracDecim(double * dSamples, int nSamples, double fdecim)
|
||||
{ // fractional decimation by fdecim > 1.0
|
||||
@@ -1851,10 +1848,8 @@ static int quisk_process_decimate(complex double * cSamples, int nSamples, int b
|
||||
static int quisk_process_demodulate(complex double * cSamples, double * dsamples, int nSamples, int bank, int nFilter, rx_mode_type rx_mode)
|
||||
{ // Changes here will require changes to get_filter_rate();
|
||||
int i;
|
||||
bool isLSB;
|
||||
complex double cx;
|
||||
double d, di, dd;
|
||||
static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0};
|
||||
//static int count=0;
|
||||
//static double phase=0;
|
||||
static struct stStorage {
|
||||
@@ -2090,6 +2085,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
MeasureSquelch[bank].squelch_active = MeasureSquelch[bank].squelch < squelch_level;
|
||||
break;
|
||||
case DGT_U: // digital mode DGT-U at 48 ksps
|
||||
case FDV_U:
|
||||
if (filter_bandwidth[nFilter] < DGT_NARROW_FREQ) { // filter at 6 ksps
|
||||
quisk_filter_srate = quisk_decim_srate / 8;
|
||||
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &Storage[bank].HalfBand5);
|
||||
@@ -2116,6 +2112,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
break;
|
||||
case DGT_L: // digital mode DGT-L
|
||||
case FDV_L:
|
||||
if (filter_bandwidth[nFilter] < DGT_NARROW_FREQ) { // filter at 6 ksps
|
||||
quisk_filter_srate = quisk_decim_srate / 8;
|
||||
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &Storage[bank].HalfBand5);
|
||||
@@ -2154,102 +2151,6 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FDV_U: // digital voice
|
||||
case FDV_L: // Extra modes added by Dave Roberts, G8KBB, June 2020. Modified by N2ADR 2024.
|
||||
quisk_check_freedv_mode();
|
||||
// current coding assumes input rate n_modem_sample_rate is 8000 or 48000.
|
||||
// decimate:
|
||||
switch (n_modem_sample_rate) {
|
||||
case 8000:
|
||||
quisk_filter_srate = quisk_decim_srate / 6;
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3);
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
|
||||
break;
|
||||
case 48000:
|
||||
quisk_filter_srate = quisk_decim_srate;
|
||||
break;
|
||||
default:
|
||||
quisk_filter_srate = quisk_decim_srate / 6;
|
||||
QuiskPrintf("Bad FreeDV modem rate in quisk_process_demodulate\n");
|
||||
break;
|
||||
}
|
||||
// filter and demodulate to mono audio:
|
||||
isLSB = (rx_mode == FDV_L);
|
||||
complex double cBuf[nSamples];
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
cx = cRxFilterOut(cSamples[i], bank, nFilter);
|
||||
if (isLSB)
|
||||
dd = creal(cx) + cimag(cx);
|
||||
else
|
||||
dd = creal(cx) - cimag(cx);
|
||||
if(bank == 0) {
|
||||
measure_audio_sum += dd * dd;
|
||||
measure_audio_count += 1;
|
||||
}
|
||||
cBuf[i] = dd;
|
||||
}
|
||||
// Perhaps decimate by an additional fraction
|
||||
if (quisk_decim_srate != 48000) {
|
||||
dd = quisk_decim_srate / 48000.0;
|
||||
nSamples = cFracDecim(cBuf, nSamples, dd);
|
||||
quisk_decim_srate = 48000;
|
||||
}
|
||||
if (bank == 0)
|
||||
process_agc(&Agc1, cBuf, nSamples, 0);
|
||||
else
|
||||
process_agc(&Agc2, cBuf, nSamples, 0);
|
||||
// pass data decimated down to n_modem_sample_rate
|
||||
// return data is at n_speech_sample_rate
|
||||
#if 0
|
||||
static double dmax_in = 0;
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
dd = fabs(creal(cBuf[i])) / CLIP16;
|
||||
if (dd > dmax_in)
|
||||
dmax_in = dd;
|
||||
}
|
||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||
//printf("Max sample in %.0lf\n", dmax_in);
|
||||
dmax_in = 0;
|
||||
}
|
||||
#endif
|
||||
if (nSamples > n_max_modem_samples) {
|
||||
printf ("FreeDV: nSamples %d exceeds max_modem_samples %d\n", nSamples, n_max_modem_samples);
|
||||
nSamples = n_max_modem_samples;
|
||||
}
|
||||
short sBuf[nSamples];
|
||||
for (i = 0; i < nSamples; i++)
|
||||
sBuf[i] = (short)(creal(cBuf[i]) / CLIP16);
|
||||
if (freedv_monitor) {
|
||||
for (i = 0; i < nSamples; i++)
|
||||
dsamples[i] = (float)sBuf[i] * CLIP16;
|
||||
}
|
||||
else {
|
||||
if (pt_quisk_freedv_rx)
|
||||
nSamples = (* pt_quisk_freedv_rx)(sBuf, dsamples, nSamples, bank);
|
||||
}
|
||||
#if 0
|
||||
static double dmax_out = 0;
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
dd = fabs(dsamples[i]) / CLIP16;
|
||||
if (dd > dmax_out)
|
||||
dmax_out = dd;
|
||||
}
|
||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||
//printf(" Max sample out %.0lf\n", dmax_out);
|
||||
dmax_out = 0;
|
||||
}
|
||||
#endif
|
||||
// current coding assumes output rate n_speech_sample_rate is 8000 or 16000
|
||||
switch (n_speech_sample_rate) {
|
||||
case 8000:
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
break;
|
||||
case 16000:
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||
measured_audio = sqrt(measure_audio_sum / measure_audio_count) / CLIP32 * 1e6;
|
||||
@@ -2785,9 +2686,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]);
|
||||
@@ -2945,6 +2843,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
|
||||
@@ -2953,10 +2853,6 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args)
|
||||
case DGT_IQ: // digital mode at 48 ksps
|
||||
filter_srate = decim_srate;
|
||||
break;
|
||||
case FDV_U: // digital voice
|
||||
case FDV_L:
|
||||
filter_srate = n_modem_sample_rate;
|
||||
break;
|
||||
}
|
||||
//QuiskPrintf("Filter rate %d\n", filter_srate);
|
||||
return PyInt_FromLong(filter_srate);
|
||||
@@ -3196,14 +3092,13 @@ static PyObject * set_params(PyObject * self, PyObject * args, PyObject * keywds
|
||||
{ /* Call with keyword arguments ONLY; change local parameters */
|
||||
static char * kwlist[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip",
|
||||
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause",
|
||||
"freedv_monitor", NULL} ;
|
||||
NULL} ;
|
||||
int i, nbytes, read_error, clip, bscope_size, hermes_pause;
|
||||
|
||||
nbytes = read_error = clip = bscope_size = hermes_pause = -1;
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidii", kwlist,
|
||||
&quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip,
|
||||
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause,
|
||||
&freedv_monitor))
|
||||
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause))
|
||||
return NULL;
|
||||
if (nbytes != -1) {
|
||||
py_sample_rx_bytes = nbytes;
|
||||
@@ -3244,31 +3139,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;
|
||||
}
|
||||
|
||||
@@ -3816,19 +3723,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;
|
||||
@@ -3851,7 +3763,7 @@ 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
|
||||
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;
|
||||
@@ -6269,13 +6181,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."},
|
||||
|
||||
@@ -312,10 +312,6 @@ extern int quisk_active_sidetone; // Whether and how to generate a sidetone
|
||||
extern int quisk_isFDX; // Are we in full duplex mode?
|
||||
extern int quisk_use_serial_port; // Are we using the serial port for CW key or PTT?
|
||||
extern play_state_t quisk_play_state; // startup, receiving, sidetone
|
||||
extern int freedv_current_mode; // current FreeDV mode; 700D, @)@) etc.
|
||||
extern int n_modem_sample_rate; // Receive data, decimate to modem_sample_rate, FreeDV codec output data at speech_sample_rate
|
||||
extern int n_speech_sample_rate; // Microphone decimate to speech_sample_rate, Freedv codec output data at modem_sample_rate, interpolate to 48000
|
||||
extern int n_max_modem_samples; // maximum input to freedv_rx()
|
||||
extern int quisk_start_cw_delay; // milliseconds to delay output on serial or MIDI CW key down
|
||||
extern int quisk_start_ssb_delay; // milliseconds to discard output for all modes except CW
|
||||
extern struct sound_dev * quiskPlaybackDevices[]; // array of Playback sound devices
|
||||
@@ -346,14 +342,7 @@ 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 *);
|
||||
@@ -397,7 +386,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);
|
||||
@@ -407,12 +395,6 @@ void quisk_set_play_state(void);
|
||||
void quisk_poll_hardware_key(void);
|
||||
void PreDistort(complex double * amp_in_samples, complex double * amp_out_samples, int nSamples, complex double * tx_samples, int num_tx);
|
||||
|
||||
// Functions supporting digital voice codecs
|
||||
typedef int (* ty_dvoice_codec_rx)(short *, double *, int, int);
|
||||
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int, int);
|
||||
extern ty_dvoice_codec_rx pt_quisk_freedv_rx;
|
||||
extern ty_dvoice_codec_tx pt_quisk_freedv_tx;
|
||||
|
||||
// Driver function definitions=================================================
|
||||
int quisk_read_alsa(struct sound_dev *, complex double *);
|
||||
void quisk_play_alsa(struct sound_dev *, int, complex double *, int, double);
|
||||
@@ -468,7 +450,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])
|
||||
@@ -483,7 +465,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);
|
||||
|
||||
@@ -292,9 +292,9 @@ class HamlibHandlerSerial:
|
||||
|
||||
# Additional logic contributed by Dr. Karsten Schmidt
|
||||
|
||||
Mo2CoKen = {'CWL':7, 'CWU':3, 'LSB':1, 'USB':2, 'AM':5, 'FM':4, 'DGT-U':9, 'DGT-L':6, 'DGT-FM':4, 'DGT-IQ':9}
|
||||
Mo2CoKen = {'CWL':7, 'CWU':3, 'LSB':1, 'USB':2, 'AM':5, 'FM':4, 'DGT-U':9, 'FDV-U':9, 'DGT-L':6, 'FDV-L':6, 'DGT-FM':4, 'DGT-IQ':9}
|
||||
Co2MoKen = {1:'LSB', 2:'USB', 3:'CWU', 4:'FM', 5:'AM', 6:'DGT-L', 7:'CWL', 9:'DGT-U'}
|
||||
Mo2CoFlex = {'CWL':3, 'CWU':4, 'LSB':0, 'USB':1, 'AM':6, 'FM':5, 'DGT-U':7, 'DGT-L':9, 'DGT-FM':5, 'DGT-IQ':7}
|
||||
Mo2CoFlex = {'CWL':3, 'CWU':4, 'LSB':0, 'USB':1, 'AM':6, 'FM':5, 'DGT-U':7, 'FDV-U':7, 'DGT-L':9, 'FDV-L':9, 'DGT-FM':5, 'DGT-IQ':7}
|
||||
Co2MoFlex = {0:'LSB', 1:'USB', 3:'CWL', 4:'CWU', 5:'FM', 6:'AM', 7:'DGT-U', 9:'DGT-L'}
|
||||
ConvertZZAC_P1_2_Step = { 0: 1,
|
||||
1: 10,
|
||||
@@ -995,17 +995,12 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
else:
|
||||
self.Send('0\n')
|
||||
def GetFreq(self): # The Rx frequency
|
||||
rx = self.app.multi_rx_screen.receiver_list
|
||||
if rx:
|
||||
rx = rx[0]
|
||||
self.Reply('Frequency', rx.txFreq + rx.VFO, 0)
|
||||
if HAMLIB_DEBUG:
|
||||
print ("GetFreq rx", rx.txFreq, rx.VFO)
|
||||
else:
|
||||
self.Reply('Frequency', self.app.rxFreq + self.app.VFO, 0)
|
||||
if HAMLIB_DEBUG:
|
||||
print ("GetFreq app", self.app.rxFreq, self.app.txFreq, self.app.VFO)
|
||||
# This is always the main Rx frequency without reference to added receivers.
|
||||
self.Reply('Frequency', self.app.rxFreq + self.app.VFO, 0)
|
||||
if HAMLIB_DEBUG:
|
||||
print ("GetFreq app", self.app.rxFreq, self.app.txFreq, self.app.VFO)
|
||||
def SetFreq(self): # The Rx frequency
|
||||
# This is always the main Rx frequency without reference to added receivers.
|
||||
freq = self.GetParamNumber()
|
||||
try:
|
||||
freq = float(freq)
|
||||
@@ -1014,12 +1009,7 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
self.ErrParam()
|
||||
else:
|
||||
freq = int(freq + 0.5)
|
||||
# For multiple receivers this controls the first added receiver frequency; else it controls split Rx/Tx frequency.
|
||||
rx = self.app.multi_rx_screen.receiver_list
|
||||
if rx:
|
||||
rx[0].ChangeRxTxFrequency(freq)
|
||||
else:
|
||||
self.app.ChangeRxTxFrequency(freq, None)
|
||||
self.app.ChangeRxTxFrequency(freq, None)
|
||||
def GetSplitFreq(self): # The Tx Frequency
|
||||
self.Reply('TX Frequency', self.app.txFreq + self.app.VFO, 0)
|
||||
def SetSplitFreq(self): # The Tx Frequency
|
||||
@@ -1061,9 +1051,9 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
mode = 'CW'
|
||||
elif mode == 'CWL': # Is this what CWR means?
|
||||
mode = 'CWR'
|
||||
elif mode == 'DGT-U':
|
||||
elif mode in ('DGT-U', 'FDV-U'):
|
||||
mode = 'PKTUSB'
|
||||
elif mode == 'DGT-L':
|
||||
elif mode in ('DGT-L', 'FDV-L'):
|
||||
mode = 'PKTLSB'
|
||||
elif mode == 'DGT-FM':
|
||||
mode = 'PKTFM'
|
||||
@@ -3727,8 +3717,8 @@ class App(wx.App):
|
||||
'txAudioPreemphUsb', 'txAudioPreemphAm', 'txAudioPreemphFm', 'txAudioPreemphFdv',
|
||||
'wfallScaleZ', 'wfallGrScaleZ', 'graphScaleZ', 'split_rxtx_play', 'modeFilter',
|
||||
'file_name_rec_audio', 'file_name_rec_samples', 'file_name_rec_mic', 'file_name_rec_tmp',
|
||||
'file_name_play_audio', 'file_name_play_samples', 'file_name_play_cq', 'freedv_mode',
|
||||
'file_play_source', 'hermes_LNA_dB']
|
||||
'file_name_play_audio', 'file_name_play_samples', 'file_name_play_cq',
|
||||
'file_play_source', 'hermes_LNA_dB', 'hermes_atten_dB']
|
||||
def __init__(self):
|
||||
global application
|
||||
QS.AppStatus(1)
|
||||
@@ -3948,9 +3938,8 @@ class App(wx.App):
|
||||
self.hot_key_ptt_active = False
|
||||
self.serial_ptt_active = False
|
||||
self.vox_ptt_active = False
|
||||
self.freedv_mode = 'Mode 700D' # restore FreeDV mode setting
|
||||
self.freedv_menu = None
|
||||
self.hermes_LNA_dB = 20
|
||||
self.hermes_atten_dB = 0
|
||||
if hasattr(Hardware, "OnChangeRxTx"):
|
||||
self.want_RxTx = True
|
||||
else:
|
||||
@@ -4711,8 +4700,6 @@ class App(wx.App):
|
||||
self.hamlib_com1_handler.close()
|
||||
if self.hamlib_com2_handler:
|
||||
self.hamlib_com2_handler.close()
|
||||
if conf.add_freedv_button:
|
||||
QS.freedv_close()
|
||||
msg = QS.GetQuiskPrintf()
|
||||
if msg:
|
||||
print(msg, end='')
|
||||
@@ -5051,14 +5038,9 @@ class App(wx.App):
|
||||
self.config_screen.config.EnableRecPlay()
|
||||
### Right bank of buttons
|
||||
mode_names = ['CWL', 'CWU', 'LSB', 'USB', 'AM', 'FM', 'DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ', 'FDV-U', 'FDV-L', 'IMD']
|
||||
labels = [('CWL', 'CWU'), ('LSB', 'USB'), 'AM', 'FM', ('DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ')]
|
||||
shortcuts = ['C', 'S', 'A', 'M', 'D']
|
||||
count = 5 # There is room for seven buttons
|
||||
if conf.add_freedv_button:
|
||||
n_freedv = count
|
||||
count += 1
|
||||
labels.append('FDV-U')
|
||||
shortcuts.append('F')
|
||||
labels = [('CWL', 'CWU'), ('LSB', 'USB'), 'AM', 'FM', ('DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ'), ('FDV-U', 'FDV-L')]
|
||||
shortcuts = ['C', 'S', 'A', 'M', 'D', 'F']
|
||||
count = 6 # There is room for seven buttons
|
||||
if conf.add_imd_button:
|
||||
n_imd = count
|
||||
count += 1
|
||||
@@ -5080,43 +5062,11 @@ class App(wx.App):
|
||||
self.modeButns.buttons[0].idName = "CW U/L"
|
||||
self.modeButns.buttons[1].idName = "SSB U/L"
|
||||
self.modeButns.buttons[4].idName = "DGT"
|
||||
self.modeButns.buttons[5].idName = "FDV"
|
||||
else:
|
||||
labels = ['CWL', 'CWU', 'LSB', 'USB', 'AM', 'FM', 'DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ', 'FDV-U', 'IMD']
|
||||
self.modeButns = RadioBtnPopup(frame, self.OnBtnMode, labels, None, 'modeButns')
|
||||
self.modeButns.idName = "modeButns"
|
||||
self.freedv_menu_items = {}
|
||||
if conf.add_freedv_button:
|
||||
self.freedv_menu = QuiskMenu("freedv_menu")
|
||||
msg = conf.freedv_tx_msg
|
||||
QS.freedv_set_options(mode=conf.freedv_modes[0][1], tx_msg=msg, DEBUG=0, squelch=1)
|
||||
self.freedv_menu.AppendCheckItem("Monitor", self.OnFreedvMonitor)
|
||||
self.freedv_menu.AppendSeparator()
|
||||
for mode, index in conf.freedv_modes:
|
||||
item = self.freedv_menu.AppendRadioItem(mode, self.OnFreedvMenu, mode == self.freedv_mode)
|
||||
self.freedv_menu_items[index] = item
|
||||
if mode == self.freedv_mode: # Restore mode
|
||||
QS.freedv_set_options(mode=index)
|
||||
if mode in ("Mode 2020", "Mode 2400A", "Mode 2400B"):
|
||||
item.Enable(False)
|
||||
if conf.button_layout == 'Large screen':
|
||||
b = QuiskCycleCheckbutton(frame, None, ('FDV-U', 'FDV-L'), is_radio=True)
|
||||
b.idName = "FDV"
|
||||
b.char_shortcut = 'F'
|
||||
self.btnFreeDV = WrapMenu(b, self.freedv_menu)
|
||||
self.modeButns.ReplaceButton(n_freedv, self.btnFreeDV)
|
||||
else:
|
||||
self.btnFreeDV = self.modeButns.AddMenu('FDV-U', self.freedv_menu)
|
||||
try:
|
||||
ok = QS.freedv_open()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
ok = 0
|
||||
if not ok:
|
||||
conf.add_freedv_button = False
|
||||
if conf.button_layout == 'Large screen':
|
||||
self.modeButns.GetButtons()[n_freedv].Enable(0)
|
||||
else:
|
||||
self.modeButns.Enable('FDV-U', False)
|
||||
if conf.add_imd_button:
|
||||
val = 1000
|
||||
QS.set_imd_level(val)
|
||||
@@ -5395,16 +5345,6 @@ class App(wx.App):
|
||||
t = '%13.2f' % (QS.measure_frequency(-1) + vfo)
|
||||
t = t[0:4] + ' ' + t[4:7] + ' ' + t[7:] + ' Hz'
|
||||
self.smeter.SetLabel(t)
|
||||
def NewDVmeter(self):
|
||||
if conf.add_freedv_button:
|
||||
snr = QS.freedv_get_snr()
|
||||
txt = QS.freedv_get_rx_char()
|
||||
self.graph.ScrollMsg(txt)
|
||||
self.waterfall.ScrollMsg(txt)
|
||||
else:
|
||||
snr = 0.0
|
||||
t = " SNR %3.0f" % snr
|
||||
self.smeter.SetLabel(t)
|
||||
def NewSmeter(self):
|
||||
self.smeter_db_count += 1 # count for average
|
||||
x = QS.get_smeter()
|
||||
@@ -5574,66 +5514,6 @@ class App(wx.App):
|
||||
self.multi_rx_screen.waterfall.pane2.filter_center = center
|
||||
if self.screen is self.filter_screen:
|
||||
self.screen.NewFilter()
|
||||
def OnFreedvMonitor(self, event):
|
||||
for item in self.freedv_menu.GetMenuItems():
|
||||
if item.GetKind() == wx.ITEM_CHECK:
|
||||
if item.IsChecked():
|
||||
QS.set_params(freedv_monitor=1)
|
||||
else:
|
||||
QS.set_params(freedv_monitor=0)
|
||||
break
|
||||
def OnFreedvMenu(self, event):
|
||||
text = ''
|
||||
for item in self.freedv_menu.GetMenuItems():
|
||||
kind = item.GetKind()
|
||||
if kind != wx.ITEM_RADIO:
|
||||
continue
|
||||
if item.IsChecked():
|
||||
text = item.GetItemLabel()
|
||||
break
|
||||
for mode, index in conf.freedv_modes:
|
||||
if mode == text:
|
||||
break
|
||||
else:
|
||||
print ("Failure in OnFreedvMenu")
|
||||
return
|
||||
mode = QS.freedv_set_options(mode=index)
|
||||
if mode == index:
|
||||
self.freedv_mode = text
|
||||
elif not self.remote_control_slave: # change to new mode failed
|
||||
self.freedv_menu_items[mode].Check(1)
|
||||
pos = (self.width//2, self.height//2)
|
||||
if index == 8:
|
||||
dlg = wx.MessageDialog(self.main_frame, "Quisk does not install %s. Please install a system-wide codec2." % text, "FreeDV Modes", wx.OK, pos)
|
||||
else:
|
||||
dlg = wx.MessageDialog(self.main_frame, "No codec2 support for " + text, "FreeDV Modes", wx.OK, pos)
|
||||
dlg.ShowModal()
|
||||
else:
|
||||
self.freedv_menu_items[mode].Check(1)
|
||||
print ("FreeDV change mode failed.")
|
||||
self.OnChangeFilter()
|
||||
def OnChangeFilter(self):
|
||||
# Used by OnFreedvMenu because the FreeDV mode may change the filter sample rate
|
||||
bw = int(self.filterButns.GetLabel())
|
||||
# We can not use QS.get_filter_srate() because the FreeDV mode is not changed yet.
|
||||
if self.freedv_mode in ("Mode 2400A", "Mode 2400B"):
|
||||
frate = 48000;
|
||||
else:
|
||||
frate = 8000;
|
||||
bw = min(bw, frate // 2)
|
||||
self.filter_bandwidth = bw
|
||||
center = self.GetFilterCenter(self.mode, bw)
|
||||
filtI, filtQ = self.MakeFilterCoef(frate, None, bw, center)
|
||||
lower_edge = center - bw // 2
|
||||
QS.set_filters(filtI, filtQ, bw, lower_edge, 0)
|
||||
self.multi_rx_screen.graph.filter_bandwidth = bw
|
||||
self.multi_rx_screen.graph.filter_center = center
|
||||
self.multi_rx_screen.waterfall.pane1.filter_bandwidth = bw
|
||||
self.multi_rx_screen.waterfall.pane1.filter_center = center
|
||||
self.multi_rx_screen.waterfall.pane2.filter_bandwidth = bw
|
||||
self.multi_rx_screen.waterfall.pane2.filter_center = center
|
||||
if self.screen is self.filter_screen:
|
||||
self.screen.NewFilter()
|
||||
def OnBtnHelp(self, event):
|
||||
if event.GetEventObject().GetValue():
|
||||
self.OnBtnScreen(None, 'Help')
|
||||
@@ -6098,11 +5978,6 @@ class App(wx.App):
|
||||
QS.set_squelch(self.levelSquelch / 12.0 - 120.0)
|
||||
else:
|
||||
QS.set_squelch(-999.0)
|
||||
elif self.mode[0:3] == 'FDV':
|
||||
if value:
|
||||
QS.freedv_set_squelch_en(1)
|
||||
else:
|
||||
QS.freedv_set_squelch_en(0)
|
||||
else:
|
||||
self.levelSquelchSSB = btn.slider_value
|
||||
if value:
|
||||
@@ -6896,10 +6771,7 @@ class App(wx.App):
|
||||
d = 1E-10
|
||||
self.smeter.SetLabel(" ADC %.0f%% %.0fdB" % (d * 100.0, 20 * math.log10(d)))
|
||||
elif self.smeter_usage == "smeter": # update the S-meter
|
||||
if self.mode in ('FDV-U', 'FDV-L'):
|
||||
self.NewDVmeter()
|
||||
else:
|
||||
self.NewSmeter()
|
||||
self.NewSmeter()
|
||||
elif self.smeter_usage == "freq":
|
||||
self.MeasureFrequency() # display measured frequency
|
||||
else:
|
||||
@@ -6927,7 +6799,7 @@ class App(wx.App):
|
||||
if self.timer - self.heart_time0 > 0.10: # call hardware to perform background tasks:
|
||||
self.heart_time0 = self.timer
|
||||
Hardware.HeartBeat()
|
||||
if conf.hermes_lite2_enable:
|
||||
if Hardware.is_HermesLite2:
|
||||
self.tx_inhibit = QS.get_params('quisk_tx_inhibit')
|
||||
else:
|
||||
self.tx_inhibit = 0
|
||||
|
||||
+136
-39
@@ -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,6 +401,7 @@ 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'
|
||||
|
||||
@@ -474,13 +478,6 @@ hermes_iob_rxin = 'J10 available'
|
||||
#hermes_iob_rxin = 'HL2 Rx to J9'
|
||||
#hermes_iob_rxin = 'Use J9 and J10'
|
||||
|
||||
## hermes_lite2_enable Hermes Lite2 protocol, boolean
|
||||
# This turns on some of the protocol extensions for the Hermes Lite2 that are not part
|
||||
# of the official Hermes Protocol 1. Currently this only enables Tx Inhibit (CN8).
|
||||
# Changes are immediate (no need to restart).
|
||||
hermes_lite2_enable = True
|
||||
#hermes_lite2_enable = False
|
||||
|
||||
|
||||
# These are known power meter calibration tables. This table is not present in the JSON settings file.
|
||||
power_meter_std_calibrations = {}
|
||||
@@ -491,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.
|
||||
@@ -508,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
|
||||
@@ -586,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'
|
||||
@@ -602,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'
|
||||
@@ -633,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'
|
||||
@@ -744,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'
|
||||
@@ -759,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
|
||||
@@ -828,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'
|
||||
@@ -860,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.
|
||||
@@ -1797,23 +1911,6 @@ add_extern_demod = ""
|
||||
add_fdx_button = 0
|
||||
#add_fdx_button = 1
|
||||
|
||||
## add_freedv_button Add FreeDv button, integer choice
|
||||
# These buttons add up to two additional mode buttons after CW, USB, etc.
|
||||
# Set this to add the FDV mode button for digital voice:
|
||||
add_freedv_button = 1
|
||||
#add_freedv_button = 0
|
||||
|
||||
## freedv_tx_msg FreeDv Tx message, text
|
||||
# For freedv, this is the text message to send.
|
||||
freedv_tx_msg = ''
|
||||
#freedv_tx_msg = 'N2XXX Jim, New Jersey, USA \n'
|
||||
|
||||
|
||||
# This is the list of FreeDV modes and their index number. The starting mode is the first listed.
|
||||
freedv_modes = (('Mode 1600', 0),
|
||||
('Mode 2400A', 3), ('Mode 2400B', 4), ('Mode 800XA', 5),
|
||||
('Mode 700C', 6), ('Mode 700D', 7), ('Mode 700E', 13), ('Mode 2020', 8))
|
||||
|
||||
# These are the filter bandwidths for each mode. Quisk has built-in optimized filters
|
||||
# for these values, but you can change them if you want.
|
||||
FilterBwCW = (200, 400, 600, 1000, 1500, 3000)
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
# 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.43'
|
||||
Version = '4.2.49'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
|
||||
@@ -1000,21 +1000,25 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
if (remote_control_slave)
|
||||
send_remote_radio_sound_socket(cSamples, nSamples);
|
||||
|
||||
is_DGT = rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM;
|
||||
is_DGT = (rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM || rxMode == FDV_U || rxMode == FDV_L);
|
||||
if (quisk_record_state == TMP_PLAY_SPKR_MIC)
|
||||
quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound
|
||||
else if (quisk_record_state == FILE_PLAY_SPKR_MIC)
|
||||
quisk_file_playback(cSamples, nSamples, file_play_level); // replace radio sound
|
||||
// Play the demodulated audio
|
||||
#if DEBUG_MIC != 2
|
||||
if ( ! quisk_play_sidetone(&quisk_Playback)) // play sidetone
|
||||
play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // else play radio sound
|
||||
if ( ! quisk_play_sidetone(&quisk_Playback)) { // play sidetone
|
||||
if (rxMode == FDV_U || rxMode == FDV_L)
|
||||
play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, 0); // play silence
|
||||
else
|
||||
play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // play radio sound
|
||||
}
|
||||
#endif
|
||||
if (radio_sound_socket != INVALID_SOCKET)
|
||||
send_radio_sound_socket(cSamples, nSamples, quisk_audioVolume);
|
||||
|
||||
// Play digital if required
|
||||
if (is_DGT)
|
||||
//if (is_DGT)
|
||||
play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level);
|
||||
|
||||
// Perhaps record the speaker audio to a file
|
||||
|
||||
+47
-50
@@ -64,12 +64,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.
|
||||
*/
|
||||
@@ -1043,19 +1051,23 @@ 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) {
|
||||
@@ -1070,6 +1082,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 +1128,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;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Makefile for WDSP library
|
||||
# This one creates libwdsp.a intended for statis linking
|
||||
# This one creates libwdsp.so, the shared library.
|
||||
#
|
||||
|
||||
LIBS=-lfftw3 -lpthread
|
||||
|
||||
Reference in New Issue
Block a user