Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78a1ca3d60 | ||
|
|
14a80f09d5 | ||
|
|
1c9f81254b | ||
|
|
76208643a0 | ||
|
|
31ecbce146 | ||
|
|
a1343c1264 |
@@ -1,3 +1,51 @@
|
||||
Quisk Version 4.2.38 September 2024
|
||||
====================================
|
||||
This is a bugfix release. I fixed a bug in the Hamlib serial port when using a physical TTY instead
|
||||
of a pseudo-tty. The Hamlib ZZFA command now changes the receive frequency instead of the transmit
|
||||
frequency. I fixed some window sizing issues.
|
||||
|
||||
Quisk Version 4.2.37 August 2024
|
||||
=================================
|
||||
Quisk now has a new option "Fixed tune offset" on the Config/radio/Options screen. This is meant for
|
||||
Softrock radios that need a constant audio frequency to simplify amplitude and phase corrections.
|
||||
The value zero means no change to Quisk tuning. Otherwise enter the fixed frequency offset from center.
|
||||
If you are using the obsolete "mouse_tune_method = 1" please delete it and use this new feature.
|
||||
If you use softrock_tune_vfo.py as your hardware file, you should replace it with softrock/hardware_usb.py.
|
||||
|
||||
Quisk now works on Python 3.12. And I fixed a bug in the small screen graph width.
|
||||
|
||||
Quisk Version 4.2.36 August 2024
|
||||
=================================
|
||||
I added two new color schemes by Michael, DK1MI. The On/Off button color and text color can
|
||||
now be configured. (Michael, send me the final colors). There are a few other small changes.
|
||||
|
||||
Quisk Version 4.2.35 June 2024
|
||||
===============================
|
||||
There is a new item "File play level" on the Config/radio/Sound screen to reduce the volume when playing files.
|
||||
Use it to reduce the volume of the CQ message relative to radio sound.
|
||||
|
||||
Dan, AI2M, provided a patch to compile in MacOS on Apple Silicon. Thanks Dan!
|
||||
|
||||
I am working on better amplitude and phase adjustments when using SoftRock radios. The new corrections
|
||||
screen has a spreadsheet to display the VFO and tune offset. And there is a feature to automatically
|
||||
measure the corrections when using a signal generator as a source. If you use Quisk with SoftRock or similar
|
||||
radios, please let me know what you think.
|
||||
|
||||
Quisk Version 4.2.34 May 2024
|
||||
==============================
|
||||
For the Hermes-Lite2 the inhibit Tx function (CN8) now releases the Spot button.
|
||||
|
||||
Quisk displays the band plan below the X-axis as colored bars, and there are eighteen possible colors.
|
||||
If you hover the mouse over a bar, Quisk now displays the name of the mode. Recall that there is a dialog
|
||||
box so you can set the band plan to what you want.
|
||||
|
||||
Quisk Version 4.2.33 May 2024
|
||||
==============================
|
||||
Added the get_level Hamlib command "l STRENGTH". It returns the S-meter reading. I finally found some Hamlib
|
||||
documentation, often not an easy task. The return value of STRENGTH is in dB with zero dB equal to S9.
|
||||
There are 6 dB per S-unit. So a STRENGTH of 4 is 4dB over S9, and -6, -12, -18 is S8, S7, S6. I also added
|
||||
the corresponding FlexRadio command ZZSM.
|
||||
|
||||
Quisk Version 4.2.32 April 2024
|
||||
================================
|
||||
I found and fixed a bug in the microphone filters that can result in a buffer overrun. You should update Quisk.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.32
|
||||
#Quisk version 4.2.38
|
||||
from .quisk import main
|
||||
|
||||
+426
-217
@@ -1,13 +1,9 @@
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import sys, wx, wx.lib, os, re, pickle, traceback, json
|
||||
import sys, wx, wx.lib, os, re, pickle, traceback, json, copy
|
||||
# Quisk will alter quisk_conf_defaults to include the user's config file.
|
||||
import quisk_conf_defaults as conf
|
||||
import _quisk as QS
|
||||
from quisk_widgets import QuiskPushbutton, QuiskCheckbutton, QuiskBitField, SliderBoxH, SliderBoxHH
|
||||
from quisk_widgets import FreqFormatter
|
||||
from quisk_widgets import wxVersion
|
||||
if wxVersion in ('2', '3'):
|
||||
import wx.combo as wxcombo
|
||||
@@ -170,12 +166,16 @@ class Configuration:
|
||||
radio_dict["Hermes_BandDictTx"] = {}
|
||||
def UpdateGlobals(self):
|
||||
self.RadioName = Settings[1]
|
||||
application.BandPlanC2T = {}
|
||||
if self.RadioName == "ConfigFileRadio":
|
||||
application.BandPlan = conf.BandPlan
|
||||
for mode, color in conf.BandPlanColors:
|
||||
application.BandPlanC2T[color] = mode
|
||||
elif "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
||||
mode_dict = {"End":None}
|
||||
for mode, color in Settings[4]["BandPlanColors"]:
|
||||
mode_dict[mode] = color
|
||||
application.BandPlanC2T[color] = mode
|
||||
plan = []
|
||||
for freq, mode in Settings[4]["BandPlan"]:
|
||||
freq = int(float(freq) * 1E6 + 0.1)
|
||||
@@ -188,6 +188,8 @@ class Configuration:
|
||||
application.BandPlan = plan
|
||||
else:
|
||||
application.BandPlan = conf.BandPlan
|
||||
for mode, color in conf.BandPlanColors:
|
||||
application.BandPlanC2T[color] = mode
|
||||
if "MidiNoteDict" not in Settings[4]:
|
||||
Settings[4]["MidiNoteDict"] = {}
|
||||
self.settings_changed = True
|
||||
@@ -282,10 +284,8 @@ class Configuration:
|
||||
self.settings_changed = True
|
||||
errors = errors + "Failed to set %s to %s using format %s\n" % (k, v, fmt)
|
||||
#traceback.print_exc()
|
||||
if conf.color_scheme == 'B':
|
||||
conf.__dict__.update(conf.color_scheme_B)
|
||||
elif conf.color_scheme == 'C':
|
||||
conf.__dict__.update(conf.color_scheme_C)
|
||||
if conf.color_scheme != 'A':
|
||||
conf.__dict__.update(getattr(conf, 'color_scheme_' + conf.color_scheme))
|
||||
self.RequiredValues(radio_dict) # Why not update conf too??? This only updates the radio_dict.
|
||||
if errors:
|
||||
dlg = wx.MessageDialog(None, errors,
|
||||
@@ -328,7 +328,7 @@ class Configuration:
|
||||
path = self.NormPath(path)
|
||||
if not os.path.isfile(path):
|
||||
dlg = wx.MessageDialog(None,
|
||||
"Failure for hardware file %s!" % path,
|
||||
"Can not find the hardware file %s!" % path,
|
||||
'Hardware File', wx.OK|wx.ICON_ERROR)
|
||||
ret = dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
@@ -399,11 +399,12 @@ class Configuration:
|
||||
notebk.AddPage(self.radio_page, "Radios")
|
||||
self.radios_page_start = notebk.GetPageCount()
|
||||
notebk.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanging, notebk)
|
||||
if Settings[1] in Settings[2]:
|
||||
page = RadioNotebook(notebk, Settings[1])
|
||||
notebk.AddPage(page, "*%s*" % Settings[1])
|
||||
for name in Settings[2]:
|
||||
page = RadioNotebook(notebk, name)
|
||||
if name == Settings[1]:
|
||||
notebk.AddPage(page, "*%s*" % name)
|
||||
else:
|
||||
if name != Settings[1]:
|
||||
page = RadioNotebook(notebk, name)
|
||||
notebk.AddPage(page, name)
|
||||
def GuessType(self):
|
||||
udp = conf.use_rx_udp
|
||||
@@ -477,7 +478,7 @@ class Configuration:
|
||||
for rxname, data in self.receiver_data:
|
||||
if rxname == receiver_name:
|
||||
return data
|
||||
return None
|
||||
return ()
|
||||
def GetReceiverDatum(self, receiver_name, item_name):
|
||||
for rxname, data in self.receiver_data:
|
||||
if rxname == receiver_name:
|
||||
@@ -563,6 +564,8 @@ class Configuration:
|
||||
# self.format4name with the items that Configuration understands.
|
||||
# Dicts and lists are Python objects. All other items are text, not Python objects.
|
||||
#
|
||||
# This parses the quisk_conf_defaults.py file and user-defined radios in ./*pkg/quisk_hardware.py.
|
||||
#
|
||||
# Sections start with 16 #, section name
|
||||
# self.sections is a list of [section_name, section_data]
|
||||
# section_data is a list of [data_name, text, fmt, help_text, values]
|
||||
@@ -595,11 +598,11 @@ class Configuration:
|
||||
except:
|
||||
traceback.print_exc()
|
||||
def _ParserConf(self, filename):
|
||||
re_AeqB = re.compile("^#?(\w+)\s*=\s*([^#]+)#*(.*)") # item values "a = b"
|
||||
re_AeqB = re.compile(r"^#?(\w+)\s*=\s*([^#]+)#*(.*)") # item values "a = b"
|
||||
section = None
|
||||
data_name = None
|
||||
multi_line = False
|
||||
fp = open(filename, "r")
|
||||
fp = open(filename, "r", encoding="utf8")
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
@@ -829,12 +832,10 @@ class QPowerMeterCalibration(wx.Frame):
|
||||
|
||||
## Note: The amplitude/phase adjustments have ideas provided by Andrew Nilsson, VK6JBL.
|
||||
## October 2020: changed to make RxTx frequency and VFO two independent variables.
|
||||
## June 2024: Added a Grid control and automatic receive measurement.
|
||||
class QAdjustPhase(wx.Frame):
|
||||
"""Create a window with amplitude and phase adjustment controls"""
|
||||
f_ampl = "Amplitude adjustment %.6f"
|
||||
f_phase = "Phase adjustment degrees %.6f"
|
||||
def __init__(self, parent, width, rx_tx):
|
||||
self.width = width
|
||||
self.rx_tx = rx_tx # Must be "rx" or "tx"
|
||||
if rx_tx == 'tx':
|
||||
self.is_tx = 1
|
||||
@@ -842,121 +843,232 @@ class QAdjustPhase(wx.Frame):
|
||||
else:
|
||||
self.is_tx = 0
|
||||
t = "Adjust Sound Card Receive Amplitude and Phase"
|
||||
wx.Frame.__init__(self, application.main_frame, -1, t, pos=(50, 100), style=wx.CAPTION)
|
||||
wx.Frame.__init__(self, application.main_frame, -1, t, pos=(50, 100), style=wx.CAPTION|wx.RESIZE_BORDER)
|
||||
self.client_width = application.screen_width * 5 // 10
|
||||
self.SetClientSize((self.client_width, application.screen_height * 5 // 10))
|
||||
self.new_amplitude = self.new_phase = 0.0
|
||||
self.new_tune = 0
|
||||
self.bandAmplPhase = copy.deepcopy(application.bandAmplPhase)
|
||||
self.new_cell = None
|
||||
self.dirty = False
|
||||
self.manual = True
|
||||
self.panel = wx.Panel(self)
|
||||
self.MakeControls()
|
||||
self.Redraw()
|
||||
self.Show()
|
||||
self.Bind(wx.EVT_CLOSE, self.OnBtnExit)
|
||||
QS.softrock_corrections(1)
|
||||
QS.set_ampl_phase(0.0, 0.0, self.is_tx)
|
||||
self.grid.GoToCell(0, 2)
|
||||
def MakeControls(self): # Make controls for phase/amplitude adjustment
|
||||
panel = self.panel
|
||||
width = self.width
|
||||
self.old_amplitude, self.old_phase = application.GetAmplPhase(self.rx_tx)
|
||||
self.new_amplitude, self.new_phase = self.old_amplitude, self.old_phase
|
||||
sl_max = width * 4 // 10 # maximum +/- value for slider
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
panel.SetSizer(sizer)
|
||||
sl_max = application.screen_width * 4 // 10 # maximum +/- value for slider
|
||||
self.ampl_scale = float(conf.rx_max_amplitude_correct) / sl_max
|
||||
self.phase_scale = float(conf.rx_max_phase_correct) / sl_max
|
||||
font = wx.Font(conf.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
|
||||
main_font = wx.Font(conf.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
|
||||
wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
|
||||
self.SetFont(font)
|
||||
self.SetFont(main_font)
|
||||
panel.SetFont(main_font)
|
||||
charx = self.GetCharWidth()
|
||||
self.tab1 = tab1 = charx
|
||||
chary = self.GetCharHeight()
|
||||
deltay = chary * 12 // 10
|
||||
y = chary * 3 // 10
|
||||
self.controls = []
|
||||
txt = wx.StaticText(panel, -1, "No data", pos=(tab1, y))
|
||||
self.controls.append(txt)
|
||||
y += txt.GetSize().GetHeight()
|
||||
#y += deltay * 14 // 10
|
||||
self.t_ampl = wx.StaticText(panel, -1, self.f_ampl % self.old_amplitude, pos=(tab1, y))
|
||||
self.controls.append(self.t_ampl)
|
||||
y += deltay
|
||||
fine = wx.StaticText(panel, -1, 'Fine', pos=(tab1, y))
|
||||
self.controls.append(fine)
|
||||
coarse = wx.StaticText(panel, -1, 'Coarse', pos=(tab1, y + deltay))
|
||||
self.controls.append(coarse)
|
||||
tab2 = tab1 + coarse.GetSize().GetWidth() + charx
|
||||
sliderX = width - tab2 - charx
|
||||
self.ampl1 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
||||
self.controls.append(self.ampl1)
|
||||
y += deltay
|
||||
self.ampl2 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
||||
self.controls.append(self.ampl2)
|
||||
y += deltay * 14 // 10
|
||||
self.PosAmpl(self.old_amplitude)
|
||||
self.t_phase = wx.StaticText(panel, -1, self.f_phase % self.old_phase, pos=(tab1, y))
|
||||
self.controls.append(self.t_phase)
|
||||
y += deltay
|
||||
fine = wx.StaticText(panel, -1, 'Fine', pos=(tab1, y))
|
||||
self.controls.append(fine)
|
||||
coarse = wx.StaticText(panel, -1, 'Coarse', pos=(tab1, y + deltay))
|
||||
self.controls.append(coarse)
|
||||
self.phase1 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
||||
self.controls.append(self.phase1)
|
||||
y += deltay
|
||||
self.phase2 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
||||
self.controls.append(self.phase2)
|
||||
y += deltay
|
||||
sv = QuiskPushbutton(panel, self.OnBtnSave, 'Save')
|
||||
self.controls.append(sv)
|
||||
dv = QuiskPushbutton(panel, self.OnBtnDestroyVFO, 'Destroy VFO')
|
||||
self.controls.append(dv)
|
||||
ds = QuiskPushbutton(panel, self.OnBtnDestroyALL, 'Destroy ALL')
|
||||
self.controls.append(ds)
|
||||
cn = QuiskPushbutton(panel, self.OnBtnFinished, 'Finished')
|
||||
self.controls.append(cn)
|
||||
hl = QuiskPushbutton(panel, self.OnBtnHelp, 'Help')
|
||||
self.controls.append(hl)
|
||||
w, h = ds.GetSize().Get()
|
||||
sv.SetSize((w, h))
|
||||
cn.SetSize((w, h))
|
||||
hl.SetSize((w, h))
|
||||
y += h * 5 // 10
|
||||
x = (width - w * 5) // 6
|
||||
sv.SetPosition((x, y))
|
||||
dv.SetPosition((x*2 + w, y))
|
||||
ds.SetPosition((x*3 + w*2, y))
|
||||
cn.SetPosition((x*4 + w*3, y))
|
||||
hl.SetPosition((x*5 + w*4, y))
|
||||
sv.SetBackgroundColour('light blue')
|
||||
dv.SetBackgroundColour('light blue')
|
||||
ds.SetBackgroundColour('light blue')
|
||||
cn.SetBackgroundColour('light blue')
|
||||
hl.SetBackgroundColour('light blue')
|
||||
y += h
|
||||
y += h * 4 // 10
|
||||
self.PosPhase(self.old_phase)
|
||||
self.SetClientSize(wx.Size(width, y))
|
||||
self.ampl1.Bind(wx.EVT_SCROLL, self.OnChange)
|
||||
# Create the grid heading and Destroy button
|
||||
self.cell_amph = wx.StaticText(panel, -1, "")
|
||||
font = wx.Font(conf.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
|
||||
wx.FONTWEIGHT_BOLD, False, conf.quisk_typeface)
|
||||
self.cell_amph.SetFont(font)
|
||||
self.cell_amph.SetLabel("Cell frequency %s, gain %.3f, phase %.3f\u00B0 " % (FreqFormatter(144000000), -1.123, -12.456))
|
||||
dv = wx.lib.buttons.GenButton(panel, label=" Destroy ")
|
||||
dv.SetBezelWidth(3)
|
||||
dv.SetBackgroundColour("#DDD")
|
||||
dv.Bind(wx.EVT_BUTTON, self.OnBtnDestroy)
|
||||
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sizer.Add(hbox, flag=wx.BOTTOM|wx.EXPAND, border=chary * 3 // 10)
|
||||
hbox.Add(self.cell_amph, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=chary // 2)
|
||||
hbox.Add(dv, flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=charx * 5)
|
||||
# Create the grid
|
||||
self.grid = wx.grid.Grid(panel, style=wx.BORDER_SIMPLE)
|
||||
self.grid.CreateGrid(4, 3)
|
||||
self.grid.HideRowLabels()
|
||||
self.selected_row = 0
|
||||
self.selected_col = 0
|
||||
self.cell_bg_color = self.grid.GetDefaultCellBackgroundColour()
|
||||
self.grid.SetDefaultCellFont(main_font)
|
||||
width, h = self.GetTextExtent("987-24 000")
|
||||
self.grid.SetDefaultColSize(width, True)
|
||||
self.grid.EnableEditing(False)
|
||||
self.grid.SetColLabelValue(0, "Band")
|
||||
self.grid.SetColLabelValue(1, "VFO")
|
||||
self.grid.SetColLabelValue(2, "Tune")
|
||||
self.grid.SetColSize(0, width // 2)
|
||||
self.grid.SetColSize(1, width * 14 // 10)
|
||||
num_tune = (self.client_width - width * 2) // width
|
||||
#print ("num_tune", num_tune)
|
||||
for col in range(3, num_tune + 2):
|
||||
self.grid.AppendCols()
|
||||
self.grid.SetColLabelValue(col, "Tune")
|
||||
#self.grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnGridLeftClick)
|
||||
self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridSelectCell)
|
||||
gbox = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer.Add(gbox, proportion=1, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=charx)
|
||||
gbox.Add(self.grid, proportion=1)
|
||||
# Create the New Cell text, Manual/Measure, AddCell button
|
||||
sbs = wx.StaticBoxSizer(wx.VERTICAL, panel, " New Cell")
|
||||
sizer.Add(sbs, flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT|wx.BOTTOM, border=charx*3)
|
||||
self.add_cell_text = wx.StaticText(panel, -1, "Band")
|
||||
self.add_cell_text.SetFont(main_font)
|
||||
sbs.Add(self.add_cell_text, flag=wx.LEFT|wx.TOP, border=charx)
|
||||
box0 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sbs.Add(1, charx)
|
||||
sbs.Add(box0, flag=wx.EXPAND)
|
||||
rb_manual = wx.RadioButton(panel, -1, "Manual adjustment", style=wx.RB_GROUP)
|
||||
self.Bind(wx.EVT_RADIOBUTTON, self.OnBtnManMeasure)
|
||||
rb_measure = wx.RadioButton(panel, -1, "Measure")
|
||||
if self.is_tx:
|
||||
rb_manual.Enable(False)
|
||||
rb_measure.Enable(False)
|
||||
b = wx.lib.buttons.GenButton(panel, label=" Add cell ")
|
||||
b.Bind(wx.EVT_BUTTON, self.OnBtnAddCell)
|
||||
b.SetBezelWidth(3)
|
||||
b.SetBackgroundColour("#DDD")
|
||||
box0.Add(rb_manual, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx)
|
||||
box0.Add(rb_measure, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx*5)
|
||||
box0.Add(b, flag=wx.LEFT, border=charx*5)
|
||||
# Create the amplitude slider controls
|
||||
box1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
box2 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sbs.Add(box1, flag=wx.EXPAND)
|
||||
sbs.Add(box2, flag=wx.EXPAND)
|
||||
# ST_ELLIPSIZE_END needed to work around a layout bug
|
||||
fine = wx.StaticText(panel, -1, 'Gain Fine', style=wx.ST_ELLIPSIZE_END)
|
||||
coarse = wx.StaticText(panel, -1, 'Gain Coarse', style=wx.ST_ELLIPSIZE_END)
|
||||
box1.Add(fine, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx*3)
|
||||
box2.Add(coarse, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx*3)
|
||||
self.ampl1 = wx.Slider(panel, -1, 0, -sl_max, sl_max)
|
||||
self.ampl2 = wx.Slider(panel, -1, 0, -sl_max, sl_max)
|
||||
box1.Add(self.ampl1, flag=wx.LEFT, proportion=1, border=0)
|
||||
box2.Add(self.ampl2, flag=wx.LEFT, proportion=1, border=0)
|
||||
self.ampl1.Bind(wx.EVT_SCROLL, self.OnAmpl1)
|
||||
self.ampl2.Bind(wx.EVT_SCROLL, self.OnAmpl2)
|
||||
self.phase1.Bind(wx.EVT_SCROLL, self.OnChange)
|
||||
self.phase2.Bind(wx.EVT_SCROLL, self.OnPhase2)
|
||||
# Create the phase slider controls
|
||||
box3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
box4 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sbs.Add(box3, flag=wx.EXPAND)
|
||||
sbs.Add(box4, flag=wx.EXPAND)
|
||||
fine = wx.StaticText(panel, -1, 'Phase Fine', style=wx.ST_ELLIPSIZE_END)
|
||||
coarse = wx.StaticText(panel, -1, 'Phase Coarse', style=wx.ST_ELLIPSIZE_END)
|
||||
box3.Add(fine, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx*3)
|
||||
box4.Add(coarse, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx*3)
|
||||
self.phas1 = wx.Slider(panel, -1, 0, -sl_max, sl_max)
|
||||
self.phas2 = wx.Slider(panel, -1, 0, -sl_max, sl_max)
|
||||
box3.Add(self.phas1, flag=wx.LEFT, proportion=1, border=0)
|
||||
box4.Add(self.phas2, flag=wx.LEFT, proportion=1, border=0)
|
||||
self.phas1.Bind(wx.EVT_SCROLL, self.OnPhase1)
|
||||
self.phas2.Bind(wx.EVT_SCROLL, self.OnPhase2)
|
||||
# Create the button row
|
||||
btnbox = wx.BoxSizer(wx.HORIZONTAL)
|
||||
sizer.Add(btnbox, flag=wx.EXPAND)
|
||||
btns = []
|
||||
btnbox.Add(1, 1, proportion=10)
|
||||
b = wx.lib.buttons.GenButton(panel, label=" Save ")
|
||||
btnbox.Add(b)
|
||||
btnbox.Add(1, 1, proportion=5)
|
||||
btns.append(b)
|
||||
size = b.GetSize()
|
||||
b.Bind(wx.EVT_BUTTON, self.OnBtnSave)
|
||||
b = wx.lib.buttons.GenButton(panel, label=" Exit ")
|
||||
btnbox.Add(b)
|
||||
btnbox.Add(1, 1, proportion=5)
|
||||
btns.append(b)
|
||||
b.Bind(wx.EVT_BUTTON, self.OnBtnExit)
|
||||
b = wx.lib.buttons.GenButton(panel, label=" Help ")
|
||||
btnbox.Add(b)
|
||||
btnbox.Add(1, 1, proportion=10)
|
||||
btns.append(b)
|
||||
b.Bind(wx.EVT_BUTTON, self.OnBtnHelp)
|
||||
#b.Bind(wx.EVT_BUTTON, self.OnGraphData)
|
||||
for b in btns:
|
||||
b.SetBezelWidth(3)
|
||||
b.SetBackgroundColour("#DDD")
|
||||
b.SetSize(size)
|
||||
sizer.Add(charx, chary)
|
||||
def Redraw(self):
|
||||
# Print available data points
|
||||
self.band = application.lastBand
|
||||
data = application.bandAmplPhase.get(self.band, {})
|
||||
data = data.get(self.rx_tx, [])
|
||||
if data:
|
||||
t = "Band %s VFO and frequency list\n" % self.band
|
||||
for vfo, items in data:
|
||||
t += "%10d:" % vfo
|
||||
for freq, ampl, phase in items:
|
||||
t += " %7d" % freq
|
||||
t += "\n"
|
||||
self.grid.ClearGrid()
|
||||
# Print available data cells
|
||||
row = 0
|
||||
bands = []
|
||||
self.cell_dict = {}
|
||||
for band in self.bandAmplPhase:
|
||||
if band != "Version" and self.rx_tx in self.bandAmplPhase[band]:
|
||||
bands.append(band)
|
||||
bands.sort(reverse=True)
|
||||
for band in bands:
|
||||
vfos_tunes = self.bandAmplPhase[band][self.rx_tx]
|
||||
if vfos_tunes:
|
||||
if self.grid.GetNumberRows() <= row:
|
||||
self.grid.AppendRows()
|
||||
self.grid.SetCellValue(row, 0, band)
|
||||
self.grid.SetCellAlignment(row, 0, wx.ALIGN_CENTER, wx.ALIGN_CENTER)
|
||||
for vfo, tunes in vfos_tunes:
|
||||
if self.grid.GetNumberRows() <= row:
|
||||
self.grid.AppendRows()
|
||||
self.cell_dict[(row, 0)] = band, vfo, 0.0
|
||||
self.grid.SetCellValue(row, 1, FreqFormatter(vfo))
|
||||
self.grid.SetCellAlignment(row, 1, wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
|
||||
col = 2
|
||||
for tune, ampl, phase in tunes:
|
||||
if self.grid.GetNumberCols() <= col:
|
||||
self.grid.AppendCols()
|
||||
self.grid.SetColLabelValue(col, "Tune")
|
||||
self.grid.SetCellValue(row, col, FreqFormatter(tune))
|
||||
self.grid.SetCellAlignment(row, col, wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
|
||||
self.cell_dict[(row, col)] = tune, ampl, phase
|
||||
if (vfo, tune) == self.new_cell:
|
||||
self.grid.GoToCell(row, col)
|
||||
col += 1
|
||||
row += 1
|
||||
self.new_cell = None
|
||||
self.grid.ForceRefresh()
|
||||
def SetNewCellText(self):
|
||||
vfo = FreqFormatter(application.VFO)
|
||||
self.add_cell_text.SetLabel("Band %s, Center (VFO) %s, Tune %d, Gain %5.3f, Phase %5.3f\u00B0" % (
|
||||
application.lastBand, vfo, self.new_tune, self.new_amplitude + 1.0, self.new_phase))
|
||||
def SlowHeartBeat(self):
|
||||
if self.manual:
|
||||
self.new_tune = application.txFreq
|
||||
else:
|
||||
t = "Band %s: No data." % self.band
|
||||
x, y = self.controls[0].GetPosition()
|
||||
height = self.controls[0].GetSize().GetHeight()
|
||||
self.controls[0].Destroy()
|
||||
txt = wx.StaticText(self.panel, -1, t, pos=(self.tab1, y))
|
||||
self.controls[0] = txt
|
||||
delta = txt.GetSize().GetHeight() - height
|
||||
for ctrl in self.controls[1:]:
|
||||
x, y = ctrl.GetPosition()
|
||||
y += delta
|
||||
ctrl.Move(x, y)
|
||||
h = self.GetClientSize().GetHeight()
|
||||
self.SetClientSize(wx.Size(self.width, h + delta))
|
||||
self.new_tune, gain, phase = QS.softrock_corrections(2)
|
||||
ampl = gain - 1.0
|
||||
self.new_amplitude = ampl
|
||||
phase *= 57.2958
|
||||
self.new_phase = phase
|
||||
self.PosAmpl(ampl)
|
||||
self.PosPhase(phase)
|
||||
self.SetNewCellText()
|
||||
def OnGridSelectCell(self, event=None):
|
||||
self.grid.SetCellBackgroundColour(self.selected_row, self.selected_col, self.cell_bg_color)
|
||||
if event:
|
||||
event.Skip()
|
||||
row = self.selected_row = event.GetRow()
|
||||
col = self.selected_col = event.GetCol()
|
||||
else:
|
||||
row = self.selected_row
|
||||
col = self.selected_col
|
||||
self.grid.SetCellBackgroundColour(row, col, '#FEA')
|
||||
self.grid.ForceRefresh()
|
||||
band, vfo, z = self.cell_dict.get((row, 0), ('', None, 0))
|
||||
tune, am, ph = self.cell_dict.get((row, col), (None, None, None))
|
||||
if not band or vfo is None:
|
||||
self.cell_amph.SetLabel("Cell is empty")
|
||||
elif col == 0:
|
||||
self.cell_amph.SetLabel("Band %s" % band)
|
||||
elif col == 1:
|
||||
self.cell_amph.SetLabel("Center (VFO) frequency %s" % FreqFormatter(vfo))
|
||||
elif tune is None:
|
||||
self.cell_amph.SetLabel("Cell is empty")
|
||||
else:
|
||||
self.cell_amph.SetLabel("Cell frequency %s, gain %.3f, phase %.3f\u00B0" % (FreqFormatter(vfo + tune), am + 1.0, ph))
|
||||
def PosAmpl(self, ampl): # set pos1, pos2 for amplitude
|
||||
pos2 = round(ampl / self.ampl_scale)
|
||||
remain = ampl - pos2 * self.ampl_scale
|
||||
@@ -967,83 +1079,196 @@ class QAdjustPhase(wx.Frame):
|
||||
pos2 = round(phase / self.phase_scale)
|
||||
remain = phase - pos2 * self.phase_scale
|
||||
pos1 = round(remain / self.phase_scale * 50.0)
|
||||
self.phase1.SetValue(pos1)
|
||||
self.phase2.SetValue(pos2)
|
||||
def OnChange(self, event):
|
||||
ampl = self.ampl_scale * self.ampl1.GetValue() / 50.0 + self.ampl_scale * self.ampl2.GetValue()
|
||||
if abs(ampl) < self.ampl_scale * 3.0 / 50.0:
|
||||
ampl = 0.0
|
||||
self.t_ampl.SetLabel(self.f_ampl % ampl)
|
||||
phase = self.phase_scale * self.phase1.GetValue() / 50.0 + self.phase_scale * self.phase2.GetValue()
|
||||
if abs(phase) < self.phase_scale * 3.0 / 50.0:
|
||||
phase = 0.0
|
||||
self.t_phase.SetLabel(self.f_phase % phase)
|
||||
QS.set_ampl_phase(ampl, phase, self.is_tx)
|
||||
self.new_amplitude, self.new_phase = ampl, phase
|
||||
self.phas1.SetValue(pos1)
|
||||
self.phas2.SetValue(pos2)
|
||||
def OnAmpl2(self, event): # re-center the fine slider when the coarse slider is adjusted
|
||||
ampl = self.ampl_scale * self.ampl1.GetValue() / 50.0 + self.ampl_scale * self.ampl2.GetValue()
|
||||
self.PosAmpl(ampl)
|
||||
self.OnChange(event)
|
||||
self.OnAmpl1(event)
|
||||
def OnAmpl1(self, event):
|
||||
ampl = self.ampl_scale * self.ampl1.GetValue() / 50.0 + self.ampl_scale * self.ampl2.GetValue()
|
||||
self.new_amplitude = ampl
|
||||
self.SetNewCellText()
|
||||
QS.set_ampl_phase(self.new_amplitude, self.new_phase, self.is_tx)
|
||||
def OnPhase2(self, event): # re-center the fine slider when the coarse slider is adjusted
|
||||
phase = self.phase_scale * self.phase1.GetValue() / 50.0 + self.phase_scale * self.phase2.GetValue()
|
||||
phase = self.phase_scale * self.phas1.GetValue() / 50.0 + self.phase_scale * self.phas2.GetValue()
|
||||
self.PosPhase(phase)
|
||||
self.OnChange(event)
|
||||
def OnBtnDestroyVFO(self, event): # Remove entry with the application VFO
|
||||
data = application.bandAmplPhase.get(self.band, {})
|
||||
data = data.get(self.rx_tx, [])
|
||||
for i in range(len(data)):
|
||||
if data[i][0] == application.VFO:
|
||||
del data[i]
|
||||
break
|
||||
self.Redraw()
|
||||
def OnBtnSave(self, event):
|
||||
data = application.bandAmplPhase
|
||||
if self.band not in data:
|
||||
data[self.band] = {}
|
||||
data = data[self.band]
|
||||
if self.rx_tx not in data:
|
||||
data[self.rx_tx] = []
|
||||
data = data[self.rx_tx]
|
||||
for vfo, items in data: # All items must be lists, not tuples
|
||||
if vfo == application.VFO:
|
||||
for i in range(len(items)):
|
||||
item = items[i]
|
||||
if item[0] == application.rxFreq:
|
||||
item[1] = self.new_amplitude
|
||||
item[2] = self.new_phase
|
||||
self.OnPhase1(event)
|
||||
def OnPhase1(self, event):
|
||||
phase = self.phase_scale * self.phas1.GetValue() / 50.0 + self.phase_scale * self.phas2.GetValue()
|
||||
self.new_phase = phase
|
||||
self.SetNewCellText()
|
||||
QS.set_ampl_phase(self.new_amplitude, self.new_phase, self.is_tx)
|
||||
def OnBtnAddCell(self, event):
|
||||
band = application.lastBand
|
||||
vfo = application.VFO
|
||||
self.new_cell = (vfo, self.new_tune)
|
||||
ampl = self.new_amplitude
|
||||
phase = self.new_phase
|
||||
tune = self.new_tune
|
||||
if band not in self.bandAmplPhase:
|
||||
self.bandAmplPhase[band] = {}
|
||||
if self.rx_tx not in self.bandAmplPhase[band]:
|
||||
self.bandAmplPhase[band][self.rx_tx] = []
|
||||
vfos_tunes = self.bandAmplPhase[band][self.rx_tx]
|
||||
for i in range(0, len(vfos_tunes)):
|
||||
vfo_tunes = vfos_tunes[i]
|
||||
if vfo_tunes[0] == vfo:
|
||||
tunes = vfo_tunes[1]
|
||||
for j in range(0, len(tunes)):
|
||||
if tunes[j][0] == tune:
|
||||
tunes[j] = [tune, ampl, phase]
|
||||
break
|
||||
else:
|
||||
items.append([application.rxFreq, self.new_amplitude, self.new_phase])
|
||||
items.sort()
|
||||
break
|
||||
else:
|
||||
data.append([application.VFO, [[application.rxFreq, self.new_amplitude, self.new_phase]]])
|
||||
data.sort()
|
||||
tunes.append([tune, ampl, phase])
|
||||
tunes.sort()
|
||||
self.dirty = True
|
||||
self.Redraw()
|
||||
return
|
||||
vfos_tunes.append([vfo, [[tune, ampl, phase]]])
|
||||
vfos_tunes = vfos_tunes.sort()
|
||||
self.dirty = True
|
||||
self.Redraw()
|
||||
def OnBtnDestroyALL(self, event):
|
||||
dlg = wx.MessageDialog(self, "This will destroy all data for band %s!" % self.band,
|
||||
"Destroy Data", style = wx.YES|wx.NO)
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
if self.band in application.bandAmplPhase:
|
||||
del application.bandAmplPhase[self.band]
|
||||
self.grid.ClearSelection()
|
||||
def OnBtnDestroy(self, event):
|
||||
# Create a list of cells to be deleted
|
||||
col_row = []
|
||||
# print("Rows", self.grid.GetSelectedRows())
|
||||
# print("Cols", self.grid.GetSelectedCols())
|
||||
# print("Cells", self.grid.GetSelectedCells())
|
||||
# print("Left", self.grid.GetSelectionBlockTopLeft())
|
||||
# print("Right", self.grid.GetSelectionBlockBottomRight())
|
||||
#
|
||||
# delete the cell at the grid cursor
|
||||
row = self.grid.GetGridCursorRow()
|
||||
col = self.grid.GetGridCursorCol()
|
||||
cr = col, row
|
||||
if cr not in col_row:
|
||||
col_row.append(cr)
|
||||
#rows = self.grid.GetSelectedRows() # Not used
|
||||
# delete whole columns
|
||||
cols = self.grid.GetSelectedCols()
|
||||
for col in cols:
|
||||
for row in range(0, self.grid.GetNumberRows()):
|
||||
cr = col, row
|
||||
if cr not in col_row:
|
||||
col_row.append(cr)
|
||||
# delete cells
|
||||
cells = self.grid.GetSelectedCells()
|
||||
for cell in cells:
|
||||
row, col = cell.Get()
|
||||
cr = col, row
|
||||
if cr not in col_row:
|
||||
col_row.append(cr)
|
||||
# delete blocks
|
||||
top_left_list = self.grid.GetSelectionBlockTopLeft()
|
||||
bottom_right_list = self.grid.GetSelectionBlockBottomRight()
|
||||
for i in range(len(top_left_list)):
|
||||
row1, col1 = top_left_list[i].Get()
|
||||
row2, col2 = bottom_right_list[i].Get()
|
||||
for row in range(row1, row2 + 1):
|
||||
for col in range(col1, col2 + 1):
|
||||
cr = col, row
|
||||
if cr not in col_row:
|
||||
col_row.append(cr)
|
||||
col_row.sort(reverse=True)
|
||||
changed = False
|
||||
for col, row in col_row:
|
||||
try:
|
||||
band, vfo, z = self.cell_dict[(row, 0)]
|
||||
vfos_tunes = self.bandAmplPhase[band][self.rx_tx]
|
||||
except:
|
||||
continue
|
||||
if col in (0, 1): # Destroy row
|
||||
for i in range(0, len(vfos_tunes)):
|
||||
if vfos_tunes[i][0] == vfo:
|
||||
del vfos_tunes[i]
|
||||
changed = True
|
||||
break
|
||||
else: # Destroy cell
|
||||
try:
|
||||
tune, am, ph = self.cell_dict[(row, col)]
|
||||
except:
|
||||
continue
|
||||
for i in range(0, len(vfos_tunes)):
|
||||
if vfos_tunes[i][0] != vfo:
|
||||
continue
|
||||
tunes = vfos_tunes[i][1]
|
||||
for j in range(0, len(tunes)):
|
||||
if tunes[j][0] == tune:
|
||||
del tunes[j]
|
||||
changed = True
|
||||
break
|
||||
if changed:
|
||||
self.dirty = True
|
||||
self.Redraw()
|
||||
def OnBtnFinished(self, event=None):
|
||||
self.selected_row = self.grid.GetGridCursorRow()
|
||||
self.selected_col = self.grid.GetGridCursorCol()
|
||||
self.OnGridSelectCell()
|
||||
self.grid.ClearSelection()
|
||||
def OnBtnManMeasure(self, event):
|
||||
self.manual = event.GetEventObject().GetLabel()[0:3] == "Man"
|
||||
self.ampl1.Enable(self.manual)
|
||||
self.ampl2.Enable(self.manual)
|
||||
self.phas1.Enable(self.manual)
|
||||
self.phas2.Enable(self.manual)
|
||||
if self.manual:
|
||||
QS.softrock_corrections(1)
|
||||
QS.set_ampl_phase(self.new_amplitude, self.new_phase, self.is_tx)
|
||||
else:
|
||||
QS.softrock_corrections(2)
|
||||
QS.set_ampl_phase(0.0, 0.0, self.is_tx)
|
||||
def OnBtnSave(self, event):
|
||||
application.bandAmplPhase = copy.deepcopy(self.bandAmplPhase)
|
||||
self.dirty = False
|
||||
def OnBtnExit(self, event):
|
||||
#self.OnGraphData(None)
|
||||
if self.dirty:
|
||||
dlg = wx.MessageDialog(self,
|
||||
"Your changes are not saved. Do you want to save them?", "Changes Were Made",
|
||||
wx.OK|wx.CANCEL|wx.CANCEL_DEFAULT|wx.ICON_WARNING)
|
||||
dlg.SetOKLabel("Discard Changes")
|
||||
ret = dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
if ret == wx.ID_CANCEL:
|
||||
return
|
||||
QS.softrock_corrections(0)
|
||||
ampl, phase = application.GetAmplPhase(self.rx_tx)
|
||||
QS.set_ampl_phase(ampl, phase, self.is_tx)
|
||||
application.w_phase = None
|
||||
self.Destroy()
|
||||
def OnBtnHelp(self, event=None):
|
||||
dlg = wx.MessageDialog(self,
|
||||
'The "VFO" is the frequency at the center of the graph screen. The Rx or Tx frequency is the offset from the VFO. \
|
||||
Adjust the VFO and the frequency as desired. Then adjust the sliders to minimize the image. Press "Save" when satisfied. \
|
||||
To adjust the VFO, use the band Up/Down buttons, or right click the graph at the desired VFO. \
|
||||
'For manual adjustment attach a signal generator or look at a strong signal in the band. Set the Quisk frequency to the signal frequency. \
|
||||
Adjust the amplitude and phase sliders to minimize the image. To save the adjustment, press Add Cell. \
|
||||
For automatic measurement, attach a signal generator and choose some frequencies. Press Add Cell for each one. \
|
||||
Adjustments must be made for both receive and transmit on each band. \
|
||||
No changes are made to the table of adjustments until you press Save. \
|
||||
The maximum slider adjustment range can be changed on the radio Hardware screen. \
|
||||
The other buttons will delete the data for the current VFO, or for the whole band. \
|
||||
For more information, press the main "Help" button, \
|
||||
then "Documentation", then "SoftRock".'
|
||||
For more information, press the main "Help" button, then "Documentation", then "SoftRock".'
|
||||
, "Adjustment Help", style=wx.OK)
|
||||
dlg.ShowModal()
|
||||
def OnGraphData(self, event):
|
||||
if not hasattr(self, "VFO"):
|
||||
self.VFO = []
|
||||
self.Tune = []
|
||||
self.Gain = []
|
||||
self.Phase = []
|
||||
if event:
|
||||
self.VFO.append(application.VFO)
|
||||
self.Tune.append(self.new_tune)
|
||||
self.Gain.append(self.new_amplitude + 1.0)
|
||||
self.Phase.append(self.new_phase)
|
||||
else:
|
||||
for name in ("VFO", "Tune", "Gain", "Phase"):
|
||||
print ("%s = [" % name)
|
||||
lst = getattr(self, name)
|
||||
for i in range(0, len(lst)):
|
||||
if name[0] in "VT":
|
||||
print("%d," % lst[i])
|
||||
else:
|
||||
print("%.5f," % lst[i])
|
||||
print ("]")
|
||||
|
||||
class ListEditDialog(wx.Dialog): # Display a dialog with a List-Edit control, plus Ok/Cancel
|
||||
def __init__(self, parent, title, choice, choices, width):
|
||||
@@ -2005,7 +2230,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'):
|
||||
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable',
|
||||
'fixed_tune_offset'):
|
||||
setattr(conf, name, x)
|
||||
application.ImmediateChange(name)
|
||||
elif name[0:4] in ('lin_', 'win_'):
|
||||
@@ -2022,6 +2248,9 @@ class ControlMixin:
|
||||
elif name == "digital_output_level":
|
||||
setattr(conf, name, x)
|
||||
QS.set_sparams(digital_output_level=x)
|
||||
elif name == "file_play_level":
|
||||
setattr(conf, name, x)
|
||||
QS.set_sparams(file_play_level=x)
|
||||
elif name[0:7] == 'hermes_':
|
||||
if name == 'hermes_TxLNA_dB':
|
||||
application.Hardware.ChangeTxLNA(x)
|
||||
@@ -2033,6 +2262,9 @@ class ControlMixin:
|
||||
elif hasattr(application.Hardware, "ImmediateChange"):
|
||||
setattr(conf, name, x)
|
||||
application.Hardware.ImmediateChange(name)
|
||||
elif name[0:6] == 'keyer_':
|
||||
setattr(conf, name, x)
|
||||
application.ImmediateChange(name)
|
||||
def FormatOK(self, value, fmt4): # Check formats integer, number, boolean
|
||||
ok, v = self.EvalItem(value, fmt4)
|
||||
return ok
|
||||
@@ -2435,6 +2667,7 @@ class Radios(BaseWindow): # The "Radios" first-level page
|
||||
choices = []
|
||||
for name, data in local_conf.receiver_data:
|
||||
choices.append(name)
|
||||
choices.sort()
|
||||
self.add_type = self.AddComboCtrl(2, '', choices=choices, no_edit=True)
|
||||
self.add_type.SetSelection(0)
|
||||
item = self.AddTextL(3, "and name the new radio")
|
||||
@@ -2518,11 +2751,9 @@ class Radios(BaseWindow): # The "Radios" first-level page
|
||||
choices = Settings[2][:] # can rename any available radio
|
||||
self.rename_old.SetItems(choices)
|
||||
self.rename_old.SetSelection(0)
|
||||
if "ConfigFileRadio" in choices:
|
||||
if "ConfigFileRadio" in choices: # can not delete ConfigFileRadio
|
||||
choices.remove("ConfigFileRadio")
|
||||
if Settings[1] in choices:
|
||||
choices.remove(Settings[1])
|
||||
self.delete_name.SetItems(choices) # can not delete ConfigFileRadio nor the current radio
|
||||
self.delete_name.SetItems(choices)
|
||||
self.delete_name.SetSelection(0)
|
||||
choices = Settings[2] + ["Ask me"]
|
||||
if "ConfigFileRadio" not in choices:
|
||||
@@ -2667,12 +2898,6 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
|
||||
hlp = "To delete a row, select it and press this button."
|
||||
self.AddTextButtonHelp(2, "Delete Midi row", "Delete", self.OnMidiDelete, hlp)
|
||||
self.NextRow()
|
||||
if self.section == "Timing and CW":
|
||||
if col != 1:
|
||||
col = 1
|
||||
self.NextRow()
|
||||
self.NextRow()
|
||||
self.AddTextL(1, 'Midi CW key moved to "Keys"', span=4)
|
||||
if not Keys:
|
||||
self.AddColSpacer(2, 20)
|
||||
self.AddColSpacer(5, 20)
|
||||
@@ -3431,13 +3656,13 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
|
||||
self.radio_dict = local_conf.GetRadioDict(self.radio_name)
|
||||
self.num_cols = 8
|
||||
for name, text, fmt, help_text, values in local_conf.GetSectionData('Sound'):
|
||||
if name == 'digital_output_level':
|
||||
if name in ('digital_output_level', 'file_play_level'):
|
||||
value = self.GetValue(name, self.radio_dict)
|
||||
no_edit = "choice" in fmt or fmt == 'boolean'
|
||||
txt, cb, btn = self.AddTextComboHelp(1, text, value, values, help_text, no_edit)
|
||||
cb.handler = self.OnChange
|
||||
cb.quisk_data_name = name
|
||||
break
|
||||
self.NextRow()
|
||||
self.NextRow()
|
||||
# Add the grid for the sound settings
|
||||
sizer = wx.GridBagSizer(2, 2)
|
||||
@@ -3463,8 +3688,10 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
|
||||
self.NextRow()
|
||||
choices = (("48000", "96000", "192000"), ("0", "1"), ("0", "1"), (" ", "0", "1"))
|
||||
r = 0
|
||||
if "SoftRock" in self.radio_dict['hardware_file_type']: # Samples come from sound card
|
||||
softrock = True
|
||||
if "SoftRock" in self.radio_dict['hardware_file_type']:
|
||||
softrock = True # Samples come from sound card
|
||||
elif hasattr(application.Hardware, "use_softrock"):
|
||||
softrock = application.Hardware.use_softrock
|
||||
else:
|
||||
softrock = False
|
||||
last_row = 8
|
||||
@@ -3984,28 +4211,7 @@ class BandPlanDlg(wx.Dialog, ControlMixin):
|
||||
if "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
||||
colors = Settings[4]["BandPlanColors"]
|
||||
else:
|
||||
Get = conf.__dict__.get # Get colors from the user's config file, if any.
|
||||
dflt = "#555555"
|
||||
colors = [
|
||||
['CW, General', Get("CW", dflt)],
|
||||
['CW, Extra', Get("eCW", dflt)],
|
||||
['Phone, General', Get("Phone", dflt)],
|
||||
['Phone, Extra', Get("ePhone", dflt)],
|
||||
['AM', Get("AM", dflt)],
|
||||
['Data', Get("Data", dflt)],
|
||||
['DxData', Get("DxData", dflt)],
|
||||
['RTTY', Get("RTTY", dflt)],
|
||||
['SSTV', Get("SSTV", dflt)],
|
||||
['Packet', Get("Packet", dflt)],
|
||||
['Beacons', Get("Beacons", dflt)],
|
||||
['Satellite', Get("Satellite",dflt)],
|
||||
['Repeater out', Get("Repeater", dflt)],
|
||||
['Repeater in', Get("RepInput", dflt)],
|
||||
['Simplex', Get("Simplex", dflt)],
|
||||
['Rx only', Get("RxOnly", dflt)],
|
||||
['Special', Get("Special", dflt)],
|
||||
['Other', Get("Other", dflt)]
|
||||
]
|
||||
colors = conf.BandPlanColors
|
||||
self.modes = []
|
||||
self.color_dict = {None:"End"}
|
||||
self.mode_dict = {"End":None}
|
||||
@@ -4212,6 +4418,9 @@ The mode begins at the start frequency and ends at the next frequency.'
|
||||
plan.append([hertz, color])
|
||||
Settings[4]["BandPlan"] = freq_mode
|
||||
application.BandPlan = plan
|
||||
application.BandPlanC2T = {}
|
||||
for mode, color in mode_color:
|
||||
application.BandPlanC2T[color] = mode
|
||||
local_conf.settings_changed = True
|
||||
self.EndModal(wx.ID_OK)
|
||||
def OnCancel(self, event):
|
||||
|
||||
@@ -194,7 +194,7 @@ Quisk is free open source software written in Python and C. It is hosted on the
|
||||
<br>
|
||||
<p>
|
||||
If you have Python 3 installed on your computer, just continue to use that version.
|
||||
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
||||
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</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.
|
||||
@@ -204,7 +204,7 @@ A Quisk installation is needed for each version of Python you have.
|
||||
<p>
|
||||
Windows does not include Python, so you must first install a recent version of 64-bit Python 3.
|
||||
If you already have 64-bit Python 3 installed, just keep using it.
|
||||
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
||||
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
|
||||
There may be newer versions of Python, but until support is added, use the versions listed above.
|
||||
Python is available from <a href="http://www.python.org/">http://www.python.org</a>.
|
||||
There is an option to <b>Add Python to PATH</b>. You MUST select
|
||||
@@ -239,13 +239,19 @@ python -m pip install --upgrade pyserial
|
||||
<br><br>
|
||||
python -m pip install --upgrade quisk
|
||||
<br><br>
|
||||
You should then be able to start Quisk with the command "quisk".
|
||||
You can also start Quisk with "python -m quisk".
|
||||
To create a Quisk shortcut on your desktop, right-click an empty space and select "New" and "Shortcut".
|
||||
Use "quisk.exe" as the command and "Quisk" as the name.
|
||||
The "quisk.exe" program is in <b>your_install_directory</b>\Scripts.
|
||||
Enter this as the location of the item:
|
||||
</p>
|
||||
<br>
|
||||
<pre>
|
||||
pythonw -m quisk
|
||||
</pre>
|
||||
<br>
|
||||
<p>
|
||||
and then name the shortcut, perhaps "Quisk".
|
||||
If you check the properties you will see that Windows expanded the location and start directory to the full path.
|
||||
If you are curious, Quisk and its Python source files are installed in "<b>your_install_directory</b>\Lib\site-packages\quisk".
|
||||
If you change to that directory you can run Quisk with "python quisk.py".
|
||||
If you change to that directory you can run Quisk with "pythonw quisk.py".
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
@@ -824,8 +830,14 @@ SoftRock radios use an analog mixer to change the RF signal to stereo audio, and
|
||||
A high quality sound card is advisable. The analog mixer is not perfect, and it will be necessary to adjust the I and Q
|
||||
signals to equal amplitude and 90 degrees phase difference. Use the Config/Config screen buttons to
|
||||
bring up an adjustment screen. Adjustments must be made for each band, and separately for transmit and receive.
|
||||
The adjustment depends on both the VFO frequency and the tuning offset from the VFO. See my paper
|
||||
The adjustment depends on both the VFO frequency and the tuning offset from the VFO.
|
||||
Quisk has an option to fix the tuning offset to a constant and to tune with the hardware.
|
||||
The tuning offset then has a constant correction and it is only necessary to correct for the VFO,
|
||||
a much less sensitive correction.
|
||||
See my paper
|
||||
<a href="http://james.ahlstrom.name/phase_corr.html">http://james.ahlstrom.name/phase_corr.html</a> for more information.
|
||||
<br>
|
||||
<br>
|
||||
A good strategy is to pick a VFO near the band center, and record corrections at an Rx frequency of -15000, -1000, 1000 and 15000 Hertz.
|
||||
If the corrections are sensitive to VFO, record the corrections for these same Rx frequencies at VFOs equal to
|
||||
or slightly outside the upper and lower band edges.
|
||||
@@ -834,15 +846,20 @@ You can add as many correction points as desired. The corrections are saved in t
|
||||
by hand if you are a Python expert. Otherwise you can just read the values.
|
||||
<br>
|
||||
<br>
|
||||
If you fix the tuning offset to a constant, you can create a correction for VFO at the band center, or
|
||||
the center and both edges.
|
||||
<br>
|
||||
<br>
|
||||
To create a Receive correction point for a given VFO and frequency, attach a signal generator to the SoftRock through an attenuator,
|
||||
and look at the image on the graph screen. If the signal is 3500 Hertz above the VFO, the image is 3500 Hertz below it.
|
||||
If you don't have a signal generator, find a strong station on the band and look at its image. Minimize the image by adjusting
|
||||
the amplitude and phase sliders on the adjustment screen, and then press "Save".
|
||||
Choose "Measure" to measure the corrections and then "Add Cell".
|
||||
If you don't have a signal generator, find a strong station on the band and look at its image. Choose "Manual adjustment".
|
||||
Minimize the image by adjusting the amplitude and phase sliders on the adjustment screen, and then press "Add cell".
|
||||
<br>
|
||||
<br>
|
||||
To create a Transmit correction point for a given VFO and frequency, attach the SoftRock RF output to a spectrum analyzer through an attenuator,
|
||||
and look at the image. If you don't have a spectrum analyzer use a second receiver tuned to the image. Minimize the image by adjusting
|
||||
the amplitude and phase sliders on the adjustment screen, and then press "Save".
|
||||
the amplitude and phase sliders on the adjustment screen, and then press "Add cell".
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
@@ -1468,7 +1485,10 @@ show the devices being used. Change the Fldigi playback to QuiskDigitalInput, an
|
||||
Monitor of QuiskDigitalOutput. Then everything should work. You do not need to use pavucontrol again because PulseAudio will
|
||||
remember the settings.
|
||||
<br>
|
||||
|
||||
<br>
|
||||
Michael, DK1MI/N1BSD, has written a nice description of how to set up Fldigi. It is available
|
||||
at his site <a href="https://dk1mi.radio/quisk-fldigi/">https://dk1mi.radio/quisk-fldigi/</a>.
|
||||
<br>
|
||||
<br>
|
||||
If your digital program only uses Alsa devices or you don't want to use PulseAudio,
|
||||
you can use the ALSA loopback device.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -0,0 +1,188 @@
|
||||
# This file is part of the Multus project. See https://www.multus-sdr.com/.
|
||||
# It controls what items appear on the Hardware configuration screen and
|
||||
# provides logic for each radio.
|
||||
|
||||
|
||||
################ Receivers Multus CW, A transceiver from the Multus project.
|
||||
## hardware_file_name Hardware file path, rfile
|
||||
# This is the file that contains the control logic for each radio.
|
||||
#hardware_file_name = "multuspkg/quisk_hardware.py"
|
||||
|
||||
## widgets_file_name Widget file path, rfile
|
||||
# This optional file adds additional controls for the radio.
|
||||
#widgets_file_name = ""
|
||||
|
||||
## rx_max_amplitude_correct Max ampl correct, number
|
||||
# If you get your I/Q samples from a sound card, you will need to correct the
|
||||
# amplitude and phase for inaccuracies in the analog hardware. The correction is
|
||||
# entered using the controls from the "Rx Phase" button on the config screen.
|
||||
# You must enter a positive number. This controls the range of the control.
|
||||
#rx_max_amplitude_correct = 0.2
|
||||
|
||||
## rx_max_phase_correct Max phase correct, number
|
||||
# If you get your I/Q samples from a sound card, you will need to correct the
|
||||
# amplitude and phase for inaccuracies in the analog hardware. The correction is
|
||||
# entered using the controls from the "Rx Phase" button on the config screen.
|
||||
# You must enter a positive number. This controls the range of the control in degrees.
|
||||
#rx_max_phase_correct = 10.0
|
||||
|
||||
## tx_level Tx Level, dict
|
||||
# This is the level of the Tx audio sent to SoftRock hardware after all processing as a percentage
|
||||
# number from 0 to 100.
|
||||
# The level should be below 100 to allow headroom for amplitude and phase adjustments.
|
||||
# Changes are immediate (no need to restart).
|
||||
#tx_level = {}
|
||||
|
||||
## digital_tx_level Digital Tx power %, integer
|
||||
# Digital modes reduce power by the percentage on the config screen.
|
||||
# This is the maximum value of the slider.
|
||||
#digital_tx_level = 100
|
||||
|
||||
## keyer_speed Keyer speed WPM, integer
|
||||
# This sets the keyer speed in words per minute.
|
||||
#keyer_speed = 8
|
||||
#keyer_speed = 13
|
||||
#keyer_speed = 18
|
||||
#keyer_speed = 20
|
||||
#keyer_speed = 30
|
||||
#keyer_speed = 40
|
||||
|
||||
## keyer_type Type of keyer, text choice
|
||||
# Use "Straight" mode for a straight key or a bug. Connect the tip of the key to the transceiver.
|
||||
# Otherwise, select Iambic-A or Iambic-B mode of operation.
|
||||
#keyer_type = "Straight"
|
||||
#keyer_type = "Iambic-A"
|
||||
#keyer_type = "Iambic-B"
|
||||
|
||||
## keyer_space Keyer space, text choice
|
||||
# This sets the type of spacing between elements or letters.
|
||||
# "Element" attempts to provide proper spacing between the elements of a character.
|
||||
# (The keyer always provides proper spacing between elements).
|
||||
# "Letter" turns on letter spacing. This attempts to provide proper spacing between characters.
|
||||
#keyer_space = "Element"
|
||||
#keyer_space = "Letter"
|
||||
|
||||
## keyer_weight Keyer weight, integer choice
|
||||
# A weight of 50% means that the dit time equals the inter-element time, which is standard.
|
||||
# Reducing the weight increases the inter-element time, increasing the weight reduces it.
|
||||
#keyer_weight = 50
|
||||
#keyer_weight = 25
|
||||
#keyer_weight = 75
|
||||
|
||||
## keyer_paddle Keyer paddle, text choice
|
||||
# "Normal" means the left paddle is DIT. "Reverse" means the left paddle is DAH.
|
||||
#keyer_paddle = "Normal"
|
||||
#keyer_paddle = "Reverse"
|
||||
|
||||
import configure
|
||||
import softrock
|
||||
from softrock.hardware_usb import Hardware as BaseHardware
|
||||
from softrock.hardware_usb import IN, OUT, UBYTE2, UBYTE4
|
||||
import _quisk as QS
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
# Set constant parameters here:
|
||||
conf.usb_vendor_id = 0x16c0
|
||||
conf.usb_product_id = 0x05dc
|
||||
conf.si570_i2c_address = 0x55
|
||||
conf.si570_direct_control = False
|
||||
conf.si570_xtal_freq = 114285000
|
||||
conf.repeater_delay = 0.25
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
QS.set_sparams(multus_cw_samples=1)
|
||||
self.use_softrock = True
|
||||
if DEBUG:
|
||||
print ("The name of this radio is", configure.Settings[1])
|
||||
print ("The hardware file type is", conf.hardware_file_type)
|
||||
def TransferOut(self, address, message): # message is a bytes array or an integer 0 to 255
|
||||
if isinstance(message, int):
|
||||
message = message.to_bytes(1, 'big')
|
||||
if self.usb_dev:
|
||||
self.usb_dev.ctrl_transfer(OUT, address, self.si570_i2c_address + 0x700, 0, message)
|
||||
if DEBUG:
|
||||
print ("USB send to 0x%X" % address, message)
|
||||
def open(self):
|
||||
text = BaseHardware.open(self)
|
||||
self.InitKeyer()
|
||||
return text
|
||||
def ChangeMode(self, mode):
|
||||
ret = BaseHardware.ChangeMode(self, mode)
|
||||
if mode in ("CWL", "CWU"):
|
||||
cw_mode = b'C' # Adding "b" does not affect the value sent
|
||||
if DEBUG:
|
||||
print ("Mode is CW")
|
||||
else:
|
||||
cw_mode = b'U'
|
||||
if DEBUG:
|
||||
print ("Mode is not CW")
|
||||
self.TransferOut(0x70, cw_mode)
|
||||
return ret
|
||||
def PollCwKey(self): # Called frequently by Quisk to check the CW key status
|
||||
return # Quisk is always in Rx
|
||||
def InitKeyer(self):
|
||||
# Initialize the keyer parameters
|
||||
conf = self.conf
|
||||
if not hasattr(conf, "keyer_speed"):
|
||||
conf.keyer_speed = 18
|
||||
if not hasattr(conf, "keyer_type"):
|
||||
conf.keyer_type = "Straight"
|
||||
if not hasattr(conf, "keyer_space"):
|
||||
conf.keyer_space = "Element"
|
||||
if not hasattr(conf, "keyer_weight"):
|
||||
conf.keyer_weight = 50
|
||||
if not hasattr(conf, "keyer_paddle"):
|
||||
conf.keyer_paddle = "Normal"
|
||||
# We need to initialize in case the persistent hardware values differ from the Quisk values.
|
||||
for name in ("keyer_speed", "keyer_type", "keyer_space", "keyer_weight", "keyer_paddle", "cwTone"):
|
||||
self.ImmediateChange(name)
|
||||
def ImmediateChange(self, name):
|
||||
BaseHardware.ImmediateChange(self, name)
|
||||
value = getattr(self.conf, name)
|
||||
if DEBUG:
|
||||
print ("ImmediateChange", name, value)
|
||||
if name == "keyer_speed":
|
||||
self.TransferOut(0x7B, value)
|
||||
elif name == "keyer_type":
|
||||
if value == "Straight":
|
||||
mode = 0
|
||||
elif value == "Iambic-A":
|
||||
mode = 1
|
||||
elif value == "Iambic-B":
|
||||
mode = 2
|
||||
else:
|
||||
mode = 0
|
||||
self.TransferOut(0x71, mode)
|
||||
elif name == "keyer_space":
|
||||
if value == "Element":
|
||||
spacing = 0
|
||||
elif value == "Letter":
|
||||
spacing = 1
|
||||
else:
|
||||
spacing = 0
|
||||
self.TransferOut(0x75, spacing)
|
||||
elif name == "keyer_weight":
|
||||
weight = int(value)
|
||||
self.TransferOut(0x77, weight)
|
||||
elif name == "keyer_paddle":
|
||||
if value == "Normal":
|
||||
paddle = 0
|
||||
elif value == "Reverse":
|
||||
paddle = 1
|
||||
else:
|
||||
paddle = 0
|
||||
self.TransferOut(0x73, paddle)
|
||||
elif name == "cwTone":
|
||||
if value < 500:
|
||||
tone_index = 0 # 400 Hz
|
||||
elif 500 <= value < 700:
|
||||
tone_index = 1 # 600 Hz
|
||||
elif 700 <= value < 900:
|
||||
tone_index = 2 # 800 Hz
|
||||
else:
|
||||
tone_index = 3 # 1000 Hz
|
||||
self.TransferOut(0x7F, tone_index)
|
||||
|
||||
|
||||
@@ -152,6 +152,10 @@ 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;
|
||||
static int softrock_correct_active; // 0 No correction adjustment; 1 Manual adjustment; 2 Calculate Rx corrections
|
||||
|
||||
static complex double PySampleBuf[SAMP_BUFFER_SIZE]; // buffer for samples returned from Python
|
||||
static int PySampleCount; // count of samples in buffer
|
||||
@@ -1972,7 +1976,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
||||
}
|
||||
quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
||||
//quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p4, 2);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
//quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
||||
@@ -3157,6 +3161,8 @@ static PyObject * get_params(PyObject * self, PyObject * args)
|
||||
return PyInt_FromLong(quisk_serial_ptt);
|
||||
if (strcmp(name, "hl2_txbuf_errors") == 0)
|
||||
return PyInt_FromLong(hl2_txbuf_errors);
|
||||
if (strcmp(name, "softrock_correct_active") == 0)
|
||||
return PyInt_FromLong((long)softrock_correct_active);
|
||||
if (strcmp(name, "quisk_tx_inhibit") == 0)
|
||||
return PyInt_FromLong((long)quisk_tx_inhibit);
|
||||
Py_INCREF (Py_None);
|
||||
@@ -3874,6 +3880,10 @@ static int read_rx_udp17(complex double * cSamples0) // Read samples from UDP
|
||||
fft_data * ptFFT;
|
||||
fd_set fds;
|
||||
static int block_number=0;
|
||||
static complex double dc_average = 0; // Average DC component in samples
|
||||
static complex double dc_sum = 0;
|
||||
static int dc_count = 0;
|
||||
static time_t time0 = 0;
|
||||
// Data from the receiver is little-endian
|
||||
|
||||
if ( ! rx_udp_gain_correct) { // correct for second stage CIC decimation JIM JIM
|
||||
@@ -3979,6 +3989,16 @@ static int read_rx_udp17(complex double * cSamples0) // Read samples from UDP
|
||||
if (xr & 0x100) { // channel 1
|
||||
if (quisk_invert_spectrum) // Invert spectrum
|
||||
sample = conj(sample);
|
||||
// correct the DC level
|
||||
dc_sum += sample;
|
||||
dc_count++;
|
||||
sample -= dc_average;
|
||||
if (dc_count >= 48000 && time(NULL) != time0) {
|
||||
time0 = time(NULL);
|
||||
dc_average = dc_sum / dc_count;
|
||||
dc_sum = dc_count = 0;
|
||||
//printf ("New DC average %.0lf\n", cabs(dc_average));
|
||||
}
|
||||
// Put samples into the fft input array.
|
||||
ptFFT = fft_data_array + fft_data_index;
|
||||
if ( ! (xi & 0x100)) { // zero marker for start of first block
|
||||
@@ -4246,6 +4266,13 @@ static PyObject * change_scan(PyObject * self, PyObject * args) // Called from G
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject * softrock_corrections(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{
|
||||
if (!PyArg_ParseTuple (args, "i", &softrock_correct_active))
|
||||
return NULL;
|
||||
return Py_BuildValue("idd", softrock_correct_freq, softrock_correct_gain, softrock_correct_phase);
|
||||
}
|
||||
|
||||
static PyObject * change_rates(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{ // Change to new sample rates
|
||||
|
||||
@@ -5033,6 +5060,135 @@ static PyObject * get_bandscope(PyObject * self, PyObject * args) // Called by t
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void check_channel_delay(fft_data * ptFft)
|
||||
{
|
||||
int i, maxi, freq;
|
||||
static int count=0, freq0;
|
||||
static double gain=0, phase=0;
|
||||
double d, ampl, maxa;
|
||||
static double complex * samples1=NULL, * samples2;
|
||||
|
||||
if (samples1 == NULL) {
|
||||
samples1 = (double complex *)malloc(fft_size * sizeof(double complex));
|
||||
samples2 = (double complex *)malloc(fft_size * sizeof(double complex));
|
||||
}
|
||||
for (i = 0; i < fft_size; i++) {
|
||||
samples1[i] = creal(ptFft->samples[i]);
|
||||
samples2[i] = cimag(ptFft->samples[i]);
|
||||
}
|
||||
fftw_execute_dft(quisk_fft_plan, samples1, samples1);
|
||||
fftw_execute_dft(quisk_fft_plan, samples2, samples2);
|
||||
maxa = 0;
|
||||
maxi = 0;
|
||||
for (i = 0; i < fft_size / 2; i++) {
|
||||
ampl = cabs(samples1[i]);
|
||||
if (ampl > maxa) {
|
||||
maxa = ampl;
|
||||
maxi = i;
|
||||
}
|
||||
}
|
||||
freq = maxi * fft_sample_rate / fft_size;
|
||||
//freq = maxi * 44100 / fft_size;
|
||||
gain += cabs(samples1[maxi]) / cabs(samples2[maxi]);
|
||||
d = carg(samples1[maxi]) - carg(samples2[maxi]);
|
||||
if (d > M_PI)
|
||||
d -= M_PI * 2;
|
||||
else if(d < - M_PI)
|
||||
d += M_PI * 2;
|
||||
phase += d;
|
||||
count += 1;
|
||||
if (count == 1) {
|
||||
freq0 = freq;
|
||||
}
|
||||
else if (freq != freq0) {
|
||||
gain = phase = count = 0;
|
||||
return;
|
||||
}
|
||||
if (count > 10) {
|
||||
gain /= count;
|
||||
phase /= count;
|
||||
//printf("Channel delay freq %6d, gain %6.3lf, phase %8.5lf radians, %6.3lf degrees\n",
|
||||
// freq, gain, phase, phase * 57.29578);
|
||||
printf("%6d, %6.3lf, %6.3lf\n", freq, gain, phase * 57.29578);
|
||||
gain = phase = count = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void softrock_correct_fft(fft_data * ptFft, int fixit)
|
||||
{ // See i/Q balance in Rocky: https://www.dxatlas.com/Rocky/Advanced.asp
|
||||
double ampl, r1, r2, i1, i2, Zr, Zi, Pwr, Gain, Phase, OutR, OutI, S1R, S1I, S2R, S2I;
|
||||
static double tot_ga=0, tot_ph=0;
|
||||
static int count=0;
|
||||
double maxa = 0;
|
||||
int i, maxi = 0, freq;
|
||||
double complex Z, s1, s2, Hr, Hi, S1, S2;
|
||||
double tim;
|
||||
static double time0 = 0;
|
||||
|
||||
for (i = 0; i < fft_size; i++) {
|
||||
ampl = cabs(ptFft->samples[i]);
|
||||
if (ampl > maxa) {
|
||||
maxa = ampl;
|
||||
maxi = i;
|
||||
}
|
||||
}
|
||||
if (maxi < fft_size / 2)
|
||||
freq = maxi;
|
||||
else
|
||||
freq = maxi - fft_size;
|
||||
freq = freq * fft_sample_rate / fft_size;
|
||||
s1 = ptFft->samples[maxi];
|
||||
s2 = ptFft->samples[fft_size - maxi];
|
||||
Z = s1 * s2;
|
||||
r1 = creal(s1);
|
||||
r2 = creal(s2);
|
||||
i1 = cimag(s1);
|
||||
i2 = cimag(s2);
|
||||
Pwr = r1 * r1 + i1 * i1 + r2 * r2 + i2 * i2;
|
||||
Zr = creal(Z) / Pwr;
|
||||
Zi = cimag(Z) / Pwr;
|
||||
Gain = sqrt((1.0 + 2 * Zr) / (1.0 - 2 * Zr));
|
||||
Phase = asin(Zi * (Gain * Gain + 1.0) / Gain);
|
||||
tot_ga += Gain;
|
||||
tot_ph += Phase;
|
||||
count += 1;
|
||||
tim = QuiskTimeSec();
|
||||
if (tim - time0 >= 0.5) {
|
||||
time0 = tim;
|
||||
softrock_correct_freq = freq;
|
||||
softrock_correct_gain = tot_ga / count;
|
||||
softrock_correct_phase = tot_ph / count;
|
||||
//if (softrock_correct_active)
|
||||
// printf("Freq domain Gain %10.7lf, Phase %10.7lf, %10.7lf, freq %d\n",
|
||||
// softrock_correct_gain, softrock_correct_phase, softrock_correct_phase * 57.29578, freq);
|
||||
tot_ga = tot_ph = count = 0;
|
||||
}
|
||||
if (fixit) {
|
||||
Hr = softrock_correct_gain * cos(softrock_correct_phase);
|
||||
Hi = softrock_correct_gain * sin(softrock_correct_phase);
|
||||
for (i = 1; i < fft_size / 2; i++) {
|
||||
S1 = ptFft->samples[fft_size - i];
|
||||
S2 = ptFft->samples[i];
|
||||
S1R = creal(S1);
|
||||
S1I = cimag(S1);
|
||||
S2R = creal(S2);
|
||||
S2I = cimag(S2);
|
||||
OutR = S2R + S1R - (S2I + S1I) * Hi + (S2R - S1R) * Hr;
|
||||
OutI = S2I - S1I + (S2I + S1I) * Hr + (S2R - S1R) * Hi;
|
||||
ptFft->samples[i] = OutR + I * OutI;
|
||||
S1R = creal(S2);
|
||||
S1I = cimag(S2);
|
||||
S2R = creal(S1);
|
||||
S2I = cimag(S1);
|
||||
OutR = S2R + S1R - (S2I + S1I) * Hi + (S2R - S1R) * Hr;
|
||||
OutI = S2I - S1I + (S2I + S1I) * Hr + (S2R - S1R) * Hi;
|
||||
ptFft->samples[fft_size - i] = OutR + I * OutI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the GUI thread
|
||||
{
|
||||
int i, j, k, m, n, index, ffts, ii, mm, m0, deltam;
|
||||
@@ -5105,7 +5261,10 @@ static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the G
|
||||
// Continue with FFT calculation
|
||||
for (i = 0; i < fft_size; i++) // multiply by window
|
||||
ptFft->samples[i] *= fft_window[i];
|
||||
//check_channel_delay(ptFft);
|
||||
fftw_execute_dft(quisk_fft_plan, ptFft->samples, ptFft->samples); // Calculate FFT
|
||||
if (softrock_correct_active == 2)
|
||||
softrock_correct_fft(ptFft, 0);
|
||||
// Create RMS s-meter value at known bandwidth
|
||||
// The pass band is (rx_tune_freq + filter_start_offset) to += bandwidth
|
||||
// d1 is the tune frequency
|
||||
@@ -5156,6 +5315,9 @@ static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the G
|
||||
//QuiskPrintf(" %d %.4lf At %5d to %5d place %5d to %5d for block %d\n", fft_size, scan_valid, mm, m, ii, i, ptFft->block);
|
||||
}
|
||||
else {
|
||||
// Zero frequency is at index fft_size / 2.
|
||||
// There are fft_size/2 positive frequencies and fft_size/2-1 negative frequencies.
|
||||
// The frequency at index k and (fft_size - k) are equal except for sign.
|
||||
count_fft++;
|
||||
k = 0;
|
||||
for (i = fft_size / 2; i < fft_size; i++) // Negative frequencies
|
||||
@@ -5981,6 +6143,7 @@ static PyMethodDef QuiskMethods[] = {
|
||||
{"get_filter_rate", get_filter_rate, METH_VARARGS, "Return the sample rate used for the filters."},
|
||||
{"get_tx_filter", quisk_get_tx_filter, METH_VARARGS, "Return the frequency response of the transmit filter."},
|
||||
{"get_audio_graph", get_audio_graph, METH_VARARGS, "Return a tuple of the audio graph data."},
|
||||
{"softrock_corrections", softrock_corrections, METH_VARARGS, "Control and return SoftRock amplitude and phase corrections."},
|
||||
{"measure_frequency", measure_frequency, METH_VARARGS, "Set the method, return the measured frequency."},
|
||||
{"measure_audio", measure_audio, METH_VARARGS, "Set the method, return the measured audio voltage."},
|
||||
{"get_overrange", get_overrange, METH_VARARGS, "Return the count of overrange (clip) for the ADC."},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# All QUISK software is Copyright (C) 2006-2021 by James C. Ahlstrom.
|
||||
# All QUISK software is Copyright (C) 2006-2024 by James C. Ahlstrom.
|
||||
# This free software is licensed for use under the GNU General Public
|
||||
# License (GPL), see http://www.opensource.org.
|
||||
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
|
||||
@@ -334,6 +334,7 @@ class HamlibHandlerSerial:
|
||||
self.public_name = public_name # the public name for the serial port
|
||||
self.tune_step = 1000
|
||||
if sys.platform == 'win32' or public_name.startswith("/dev/"):
|
||||
self.port_is_mod_serial = True # self.port is from the serial module
|
||||
try:
|
||||
import serial
|
||||
except:
|
||||
@@ -346,6 +347,7 @@ class HamlibHandlerSerial:
|
||||
print ("The serial port %s could not be opened." % public_name)
|
||||
else:
|
||||
import tty
|
||||
self.port_is_mod_serial = False # self.port is a file descriptor
|
||||
if os.path.lexists(public_name):
|
||||
try:
|
||||
os.remove(public_name)
|
||||
@@ -369,7 +371,7 @@ class HamlibHandlerSerial:
|
||||
def open(self):
|
||||
return
|
||||
def close(self):
|
||||
if sys.platform == 'win32':
|
||||
if self.port_is_mod_serial:
|
||||
if self.port:
|
||||
self.port.close()
|
||||
self.port = None
|
||||
@@ -382,7 +384,7 @@ class HamlibHandlerSerial:
|
||||
def Read(self):
|
||||
if self.port is None:
|
||||
return
|
||||
if sys.platform == 'win32':
|
||||
if self.port_is_mod_serial:
|
||||
text = self.port.read(99)
|
||||
if not isinstance(text, Q3StringTypes):
|
||||
text = text.decode('utf-8')
|
||||
@@ -433,22 +435,14 @@ class HamlibHandlerSerial:
|
||||
return
|
||||
if isinstance(data, Q3StringTypes):
|
||||
data = data.encode('utf-8', errors='ignore')
|
||||
if sys.platform == 'win32':
|
||||
if self.port_is_mod_serial:
|
||||
self.port.write(data)
|
||||
else:
|
||||
r, w, x = select.select((), (self.port,), (), 0)
|
||||
if w:
|
||||
os.write(self.port, data)
|
||||
def set_frequency(self, freq):
|
||||
tune = freq - self.app.VFO
|
||||
d = self.app.sample_rate * 45 // 100
|
||||
if -d <= tune <= d: # Frequency is on-screen
|
||||
vfo = self.app.VFO
|
||||
else: # Change the VFO
|
||||
vfo = (freq // 5000) * 5000 - 5000
|
||||
tune = freq - vfo
|
||||
self.app.BandFromFreq(freq)
|
||||
self.app.ChangeHwFrequency(tune, vfo, 'FreqEntry')
|
||||
def set_frequency(self, freq): # This changes the Rx frequency
|
||||
self.app.ChangeRxTxFrequency(freq, None)
|
||||
if HAMLIB_DEBUG: print("New Freq rx,tx", self.app.txFreq + self.app.VFO, self.app.rxFreq + self.app.VFO)
|
||||
def ZZAC(self, cmd, data, length): # Set or read tune steps
|
||||
if length == 0:
|
||||
@@ -658,6 +652,16 @@ class HamlibHandlerSerial:
|
||||
self.app.pttButton.SetValue(0, True)
|
||||
else:
|
||||
self.Error(cmd, data)
|
||||
def ZZSM(self, cmd, data, length): # return the S-meter value in dB * 2; 0 to 260
|
||||
if length == 0: # 0 to 260 is -140 to -10 dB; S9 == -73 dB == 134; dB = ZZSM / 2 - 140
|
||||
i = round((self.app.hamlib_strength + 67) * 2)
|
||||
if i < 0:
|
||||
i = 0
|
||||
elif i > 260:
|
||||
i = 260
|
||||
self.Write('%s%03d;' % (cmd, i))
|
||||
else:
|
||||
self.Error(cmd, data)
|
||||
def ZZSP(self, cmd, data, length): # the split status
|
||||
if length == 0:
|
||||
if self.app.split_rxtx:
|
||||
@@ -826,6 +830,8 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
"""Send text back to the client."""
|
||||
if isinstance(text, Q3StringTypes):
|
||||
text = text.encode('utf-8', errors='ignore')
|
||||
if HAMLIB_DEBUG:
|
||||
print ("Rig2 send", text)
|
||||
try:
|
||||
self.sock.sendall(text)
|
||||
except socket.error:
|
||||
@@ -932,6 +938,8 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
else:
|
||||
return 1
|
||||
self.input = self.input.strip() # Here is our command line
|
||||
if HAMLIB_DEBUG:
|
||||
print ("Rig2 received", self.input)
|
||||
while self.input:
|
||||
# Parse the commands and call the appropriate handlers
|
||||
self.params = ''
|
||||
@@ -982,8 +990,12 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
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)
|
||||
def SetFreq(self): # The Rx frequency
|
||||
freq = self.GetParamNumber()
|
||||
try:
|
||||
@@ -1151,7 +1163,7 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
def GetLevel(self):
|
||||
name = self.GetParamName()
|
||||
if name == '?': # send back supported parameters
|
||||
self.Reply2('AGC AF', 0)
|
||||
self.Reply2('AGC AF STRENGTH', 0)
|
||||
elif name == 'AGC':
|
||||
if self.app.BtnAGC.GetValue():
|
||||
self.Reply2(4, 0)
|
||||
@@ -1160,6 +1172,9 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
elif name == 'AF':
|
||||
x = self.app.audio_volume # audio_volume is 0 to 1.000
|
||||
self.Reply2("%.5f" % x, 0)
|
||||
elif name == 'STRENGTH':
|
||||
i = round(self.app.hamlib_strength)
|
||||
self.Reply2(i, 0)
|
||||
else:
|
||||
self.ErrParam()
|
||||
def SetLevel(self):
|
||||
@@ -1879,6 +1894,12 @@ class GraphScreen(wx.Window):
|
||||
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
|
||||
self.Bind(wx.EVT_MOTION, self.OnMotion)
|
||||
self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
|
||||
# handle bandplan info
|
||||
self.bandplanWindow = wx.PopupWindow (parent)
|
||||
self.bandplanInfo = wx.TextCtrl(self.bandplanWindow, style=wx.TE_READONLY)
|
||||
self.bandplanInfo.SetFont(wx.Font(conf.status_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
|
||||
wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface))
|
||||
self.bandplanWindow.Hide();
|
||||
self.MakeDisplay()
|
||||
def MakeDisplay(self):
|
||||
self.display = GraphDisplay(self, self.originX, 0, self.graph_width, 5, self.chary)
|
||||
@@ -2199,7 +2220,8 @@ class GraphScreen(wx.Window):
|
||||
self.ChangeHwFrequency(self.txFreq, self.VFO, 'MouseBtn1', event=event, rx_freq=freq)
|
||||
else:
|
||||
self.ChangeHwFrequency(freq, self.VFO, 'MouseBtn1', event=event)
|
||||
self.CaptureMouse()
|
||||
if not self.HasCapture():
|
||||
self.CaptureMouse()
|
||||
def OnLeftUp(self, event):
|
||||
if self.HasCapture():
|
||||
self.ReleaseMouse()
|
||||
@@ -2212,7 +2234,7 @@ class GraphScreen(wx.Window):
|
||||
mouse_x, mouse_y = self.GetMousePosition(event)
|
||||
if wx.GetKeyState(wx.WXK_SHIFT):
|
||||
mouse_x -= self.filter_center * self.data_width / sample_rate
|
||||
if conf.mouse_tune_method: # Mouse motion changes the VFO frequency
|
||||
if False: #conf.mouse_tune_method: # Mouse motion changes the VFO frequency
|
||||
x = (mouse_x - self.mouse_x) # Thanks to VK6JBL
|
||||
self.mouse_x = mouse_x
|
||||
freq = float(x) * sample_rate / self.data_width
|
||||
@@ -2230,6 +2252,44 @@ class GraphScreen(wx.Window):
|
||||
self.ChangeHwFrequency(self.txFreq, self.VFO, 'MouseMotion', event=event, rx_freq=freq2)
|
||||
else: # Mouse motion changes the transmit frequency
|
||||
self.ChangeHwFrequency(self.txFreq + freq, self.VFO, 'MouseMotion', event=event)
|
||||
else:
|
||||
mouse_x, mouse_y = self.GetMousePosition(event)
|
||||
if not self.in_splitter and self.originY <= mouse_y <= self.originY + self.tick and (
|
||||
self.originX <= mouse_x <= self.originX + self.graph_width):
|
||||
#sample_rate = int(self.sample_rate * self.zoom) # Measure the frequency
|
||||
#freq = float(mouse_x - self.x0) * sample_rate / self.data_width + self.zoom_deltaf + self.VFO
|
||||
#freq = int(freq)
|
||||
#color = None
|
||||
#for start_freq, start_color in application.BandPlan:
|
||||
# if start_freq >= freq:
|
||||
# break
|
||||
# color = start_color
|
||||
dc = wx.ClientDC(self) # Read the pixel at the mouse position
|
||||
pixel = dc.GetPixel(mouse_x, mouse_y)
|
||||
pixel = "#%02X%02X%02X" % (pixel[0], pixel[1], pixel[2])
|
||||
txt = application.BandPlanC2T.get(pixel, '')
|
||||
if pixel == "#000000":
|
||||
pass
|
||||
elif txt:
|
||||
self.bandplanInfo.Clear()
|
||||
self.bandplanInfo.write(txt)
|
||||
width, height = self.bandplanInfo.GetTextExtent(txt)
|
||||
width += self.bandplanInfo.GetCharWidth() * 2
|
||||
height += self.bandplanInfo.GetCharHeight() // 2
|
||||
self.bandplanInfo.SetSize(wx.Size(width, height))
|
||||
self.bandplanWindow.SetClientSize((width, height))
|
||||
rect = self.GetScreenRect()
|
||||
if mouse_x > self.originX + self.graph_width * 3 // 4:
|
||||
self.bandplanWindow.Move(mouse_x + rect.GetX() - width, mouse_y + rect.GetY() - height * 12 // 10)
|
||||
else:
|
||||
self.bandplanWindow.Move(mouse_x + rect.GetX(), mouse_y + rect.GetY() - height * 12 // 10)
|
||||
if not self.bandplanWindow.IsShown():
|
||||
self.bandplanWindow.Show()
|
||||
else:
|
||||
if self.bandplanWindow.IsShown():
|
||||
self.bandplanWindow.Hide()
|
||||
elif self.bandplanWindow.IsShown():
|
||||
self.bandplanWindow.Hide()
|
||||
def OnWheel(self, event):
|
||||
if conf.freq_spacing:
|
||||
wm = conf.freq_spacing
|
||||
@@ -2480,12 +2540,10 @@ class WaterfallDisplay(wx.Window):
|
||||
self.top_key = 8
|
||||
self.top_size = (self.top_key + 2) * (self.top_key - 1) // 2
|
||||
# Make the palette
|
||||
if conf.waterfall_palette == 'B':
|
||||
pal2 = conf.waterfallPaletteB
|
||||
elif conf.waterfall_palette == 'C':
|
||||
pal2 = conf.waterfallPaletteC
|
||||
else:
|
||||
if conf.waterfall_palette == 'A':
|
||||
pal2 = conf.waterfallPalette
|
||||
else:
|
||||
pal2 = getattr(conf, "waterfallPalette" + conf.waterfall_palette)
|
||||
red = bytearray(256)
|
||||
green = bytearray(256)
|
||||
blue = bytearray(256)
|
||||
@@ -2579,7 +2637,7 @@ class WaterfallScreen(wx.SplitterWindow):
|
||||
self.y_zero = conf.waterfall_y_zero
|
||||
self.zoom_control = 0
|
||||
wx.SplitterWindow.__init__(self, frame)
|
||||
self.SetSizeHints(width, -1, width)
|
||||
self.SetSizeHints(width, -1)
|
||||
self.SetSashGravity(0.50)
|
||||
self.SetMinimumPaneSize(1)
|
||||
self.SetSize((width, conf.waterfall_graph_size + 100)) # be able to set sash size
|
||||
@@ -2933,7 +2991,7 @@ class MultiReceiverScreen(wx.SplitterWindow):
|
||||
self.rx_zero = self.graph
|
||||
self.Initialize(self.rx_zero)
|
||||
self.waterfall.Hide()
|
||||
self.SetSizeHints(self.width, -1, self.width)
|
||||
self.SetSizeHints(self.width, -1)
|
||||
# Calculate control width
|
||||
rx_btn = QuiskPushbutton(self, None, "Rx 8....", style=wx.BU_EXACTFIT)
|
||||
self.rx_btn_width, self.rx_btn_height = rx_btn.GetSize().Get()
|
||||
@@ -3565,12 +3623,13 @@ class App(wx.App):
|
||||
self.smeter_avg_seconds = 1.0 # seconds for S-meter average
|
||||
self.smeter_sunits = -87.0
|
||||
self.smeter_usage = "smeter" # specify use of s-meter display
|
||||
self.hamlib_strength = 0.0;
|
||||
self.timer = time.time() # A seconds clock
|
||||
self.heart_time0 = self.timer # timer to call HeartBeat at intervals
|
||||
self.save_time0 = self.timer
|
||||
self.smeter_db_time0 = self.timer
|
||||
self.smeter_sunits_time0 = self.timer
|
||||
self.fewsec_time0 = self.timer
|
||||
self.slowheart_time0 = self.timer
|
||||
self.multi_rx_index = 0
|
||||
self.multi_rx_timer = self.timer
|
||||
self.band_up_down = 0 # Are band Up/Down buttons in use?
|
||||
@@ -3726,6 +3785,7 @@ class App(wx.App):
|
||||
pass #traceback.print_exc()
|
||||
if "Version" not in self.bandAmplPhase:
|
||||
self.FixAmplPhase() # Convert to new format Oct 2020
|
||||
self.FixAmplPhase2() # New logic June 2024
|
||||
if Hardware.VarDecimGetChoices(): # Hardware can change the decimation.
|
||||
self.sample_rate = Hardware.VarDecimSet() # Get the sample rate.
|
||||
self.vardecim_set = self.sample_rate
|
||||
@@ -3764,16 +3824,16 @@ class App(wx.App):
|
||||
self.std_out_err.Show(False)
|
||||
else:
|
||||
self.std_out_err.Show(True)
|
||||
#w, h = frame.GetSize().Get()
|
||||
#ww, hh = frame.GetClientSizeTuple()
|
||||
#print ('Main frame: size', w, h, 'client', ww, hh)
|
||||
wClient, hClient = self.main_frame.GetClientSize()
|
||||
wFrame, hFrame = self.main_frame.GetSize()
|
||||
main_border_w = wFrame - wClient
|
||||
#print ('Main frame: size', wFrame, hFrame, 'client', wClient, hClient, 'border', main_border_w)
|
||||
# Find the data width from a list of preferred sizes; it is the width of returned graph data.
|
||||
# The graph_width is the width of data_width that is displayed.
|
||||
# The total width of the graph is graph_width + self.graph_borders
|
||||
if conf.window_width > 0:
|
||||
#wFrame, h = frame.GetClientSize().Get() # client window width
|
||||
wFrame = conf.window_width - 15 # Thanks to Nick Abramenko, RA3PKJ
|
||||
graph = GraphScreen(frame, self.width//2, self.width//2, None) # make a GraphScreen to calculate borders
|
||||
self.graph_width = wFrame - (graph.width - graph.graph_width) # less graph borders equals actual graph_width
|
||||
self.graph_width = wClient - (graph.width - graph.graph_width) # less graph borders equals actual graph_width
|
||||
graph.Destroy()
|
||||
del graph
|
||||
if self.graph_width % 2 == 1: # Both data_width and graph_width are even numbers
|
||||
@@ -3898,14 +3958,14 @@ class App(wx.App):
|
||||
frame.SetSizer(vertBox)
|
||||
# Add the screens
|
||||
vertBox.Add(self.config_screen, 1, wx.EXPAND)
|
||||
vertBox.Add(self.multi_rx_screen, 1)
|
||||
vertBox.Add(self.scope, 1)
|
||||
vertBox.Add(self.bandscope_screen, 1)
|
||||
vertBox.Add(self.filter_screen, 1)
|
||||
vertBox.Add(self.multi_rx_screen, 1, wx.EXPAND)
|
||||
vertBox.Add(self.scope, 1, wx.EXPAND)
|
||||
vertBox.Add(self.bandscope_screen, 1, wx.EXPAND)
|
||||
vertBox.Add(self.filter_screen, 1, wx.EXPAND)
|
||||
if self.rate_audio_fft:
|
||||
vertBox.Add(self.audio_fft_screen, 1)
|
||||
vertBox.Add(self.help_screen, 1)
|
||||
vertBox.Add(self.station_screen)
|
||||
vertBox.Add(self.audio_fft_screen, 1, wx.EXPAND)
|
||||
vertBox.Add(self.help_screen, 1, wx.EXPAND)
|
||||
vertBox.Add(self.station_screen, 0, wx.EXPAND)
|
||||
# Add the spacer
|
||||
vertBox.Add(Spacer(frame), 0, wx.EXPAND)
|
||||
# Add the sizer for the controls
|
||||
@@ -3918,15 +3978,17 @@ class App(wx.App):
|
||||
vertBox.AddSpacer(5) # Thanks to Christof, DJ4CM
|
||||
# End of vertical box.
|
||||
self.MakeButtons(frame, gbs)
|
||||
minw = width = self.graph.width
|
||||
maxw = maxh = -1
|
||||
minh = 100
|
||||
if conf.window_width > 0:
|
||||
minw = width = maxw = conf.window_width
|
||||
minw = maxw = conf.window_width
|
||||
else:
|
||||
minw = self.graph.width + main_border_w
|
||||
maxw = -1
|
||||
if conf.window_height > 0:
|
||||
minh = maxh = self.height = conf.window_height
|
||||
else:
|
||||
minh = 100
|
||||
maxh = -1
|
||||
self.main_frame.SetSizeHints(minw, minh, maxw, maxh)
|
||||
self.main_frame.SetClientSize(wx.Size(width, self.height))
|
||||
if hasattr(Hardware, 'pre_open'): # pre_open() is called before open()
|
||||
Hardware.pre_open()
|
||||
if self.local_conf.GetWidgets(self, Hardware, conf, frame, gbs, vertBox):
|
||||
@@ -4392,6 +4454,25 @@ class App(wx.App):
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.bandAmplPhase[band] = {}
|
||||
def FixAmplPhase2(self): # New definition of phase
|
||||
if self.bandAmplPhase["Version"]["rx"] == [1]:
|
||||
return
|
||||
degrad = 360.0 / 2 / math.pi
|
||||
for band in self.bandAmplPhase:
|
||||
if band == "Version":
|
||||
continue
|
||||
for rx_tx in self.bandAmplPhase[band]:
|
||||
if rx_tx in ('rx', 'tx'):
|
||||
data = self.bandAmplPhase[band][rx_tx]
|
||||
for item in data:
|
||||
vfo = item[0]
|
||||
lst_framph = item[1]
|
||||
for framph in lst_framph:
|
||||
freq, am, ph = framph
|
||||
p = math.atan(math.tan(ph / degrad) / (1.0 + am))
|
||||
#print ("%5s %3s %8d %8d %8.4f %8.3f %8.3f" % (band, rx_tx, vfo, freq, am, ph, p * degrad))
|
||||
framph[2] = p * degrad
|
||||
self.bandAmplPhase["Version"]["rx"] = [1]
|
||||
def ImmediateChange(self, name):
|
||||
if name == "pulse_audio_verbose_output":
|
||||
value = getattr(conf, name)
|
||||
@@ -4630,7 +4711,7 @@ class App(wx.App):
|
||||
b.char_shortcut = 'l'
|
||||
self.MakeAccel(b)
|
||||
self.splitButton = WrapMenu(b, self.split_menu)
|
||||
if conf.mouse_tune_method: # Mouse motion changes the VFO frequency
|
||||
if False: #conf.mouse_tune_method: # Mouse motion changes the VFO frequency
|
||||
self.splitButton.Enable(False)
|
||||
left_row3.append(self.splitButton)
|
||||
b = QuiskCheckbutton(frame, self.OnBtnFDX, 'FDX', color=conf.color_test)
|
||||
@@ -4840,7 +4921,8 @@ class App(wx.App):
|
||||
self.freqDisplay.Display(self.txFreq + self.VFO)
|
||||
# On/Off button
|
||||
if conf.button_layout == 'Large screen':
|
||||
b_onoff = QuiskCheckbutton(frame, self.OnBtnOnOff, "On", color='#77DD77')
|
||||
b_onoff = QuiskCheckbutton(frame, self.OnBtnOnOff, "On", color=conf.color_onoff)
|
||||
b_onoff.text_color = conf.color_onoff_text
|
||||
b_onoff.SetValue(True, do_cmd=False)
|
||||
h = b_freqdisp.height
|
||||
b_onoff.SetSizeHints(h, h, h, h)
|
||||
@@ -5043,6 +5125,7 @@ class App(wx.App):
|
||||
self.smeter_sunits_time0 = self.timer
|
||||
s = self.smeter_sunits / 6.0 # change to S units; 6db per S unit
|
||||
s += Hardware.correct_smeter # S-meter correction for the gain, band, etc.
|
||||
self.hamlib_strength = (s - 9.0) * 6.0 # convert to dB; 0 dB is S9
|
||||
if s < 0:
|
||||
s = 0
|
||||
if s >= 9.5:
|
||||
@@ -5795,16 +5878,31 @@ class App(wx.App):
|
||||
The hardware will reply with the updated frequencies which may be different
|
||||
from those requested; use and display the returned tune and vfo.
|
||||
"""
|
||||
#print ("ChangeHW", source, tune, vfo, vfo + tune)
|
||||
if not self.split_rxtx:
|
||||
self.rxFreq = tune # rxFreq must be correct before the call to Hardware.ChangeFrequency()
|
||||
elif rx_freq is not None:
|
||||
self.rxFreq = rx_freq
|
||||
rx_offset = self.rxFreq - tune
|
||||
if self.screen == self.bandscope_screen:
|
||||
freq = vfo + tune
|
||||
tune = freq % 10000
|
||||
vfo = freq - tune
|
||||
tune, vfo = Hardware.ChangeFrequency(vfo + tune, vfo, source, band, event)
|
||||
self.ChangeDisplayFrequency(tune - vfo, vfo, rx_freq is not None)
|
||||
tune, vfo = Hardware.ChangeFrequency(vfo + tune, vfo, source, band, event)
|
||||
self.ChangeDisplayFrequency(tune - vfo, vfo, rx_freq is not None)
|
||||
elif conf.fixed_tune_offset:
|
||||
# Tune with the VFO and keep a constant audio offset from the center. Tx tune is always conf.fixed_tune_offset.
|
||||
tune_freq = vfo + tune
|
||||
self.rxFreq = conf.fixed_tune_offset + rx_offset
|
||||
if source == "BtnUpDown":
|
||||
tune_freq = vfo + conf.fixed_tune_offset
|
||||
else:
|
||||
vfo = tune_freq - conf.fixed_tune_offset
|
||||
tune_freq, vfo = Hardware.ChangeFrequency(tune_freq, vfo, source, band, event)
|
||||
self.ChangeDisplayFrequency(tune_freq - vfo, vfo, True)
|
||||
else:
|
||||
tune, vfo = Hardware.ChangeFrequency(vfo + tune, vfo, source, band, event)
|
||||
self.ChangeDisplayFrequency(tune - vfo, vfo, rx_freq is not None)
|
||||
def ChangeDisplayFrequency(self, tune, vfo, new_rxfreq=False):
|
||||
'Change the frequency displayed by Quisk'
|
||||
change = 0
|
||||
@@ -5824,10 +5922,8 @@ class App(wx.App):
|
||||
self.graph.SetVFO(vfo)
|
||||
self.waterfall.SetVFO(vfo)
|
||||
self.station_screen.Refresh()
|
||||
if self.w_phase:
|
||||
self.w_phase.Redraw()
|
||||
if change:
|
||||
if conf.name_of_sound_capt or conf.name_of_mic_play:
|
||||
if (conf.name_of_sound_capt or conf.name_of_mic_play) and QS.get_params("softrock_correct_active") == 0:
|
||||
ampl, phase = self.GetAmplPhase('rx')
|
||||
QS.set_ampl_phase(ampl, phase, 0)
|
||||
ampl, phase = self.GetAmplPhase('tx')
|
||||
@@ -6303,7 +6399,7 @@ class App(wx.App):
|
||||
for client in self.hamlib_clients: # Service existing clients
|
||||
if not client.Process(): # False return indicates a closed connection; remove the handler for this client
|
||||
self.hamlib_clients.remove(client)
|
||||
# print 'Remove', client.address
|
||||
# print ('Remove', client.address)
|
||||
break
|
||||
def OnKeyHook(self, event):
|
||||
event.Skip()
|
||||
@@ -6519,6 +6615,8 @@ class App(wx.App):
|
||||
self.old_tx_inhibit = self.tx_inhibit
|
||||
self.graph.display.Refresh()
|
||||
self.waterfall.pane1.display.Refresh()
|
||||
if self.tx_inhibit and self.spotButton.GetValue():
|
||||
self.spotButton.SetValue(0, True)
|
||||
msg = QS.GetQuiskPrintf()
|
||||
if msg:
|
||||
print(msg, end='')
|
||||
@@ -6537,9 +6635,10 @@ class App(wx.App):
|
||||
self.ChangeDisplayFrequency(tune - vfo, vfo)
|
||||
self.FldigiPoll()
|
||||
self.HamlibPoll()
|
||||
#if self.timer - self.fewsec_time0 > 3.0:
|
||||
# self.fewsec_time0 = self.timer
|
||||
# print ('fewswc')
|
||||
if self.timer - self.slowheart_time0 > 0.5:
|
||||
self.slowheart_time0 = self.timer
|
||||
if self.w_phase:
|
||||
self.w_phase.SlowHeartBeat()
|
||||
if self.timer - self.save_time0 > 20.0:
|
||||
self.save_time0 = self.timer
|
||||
if self.CheckState():
|
||||
|
||||
+124
-4
@@ -1084,7 +1084,15 @@ digital_rx1_name = ""
|
||||
## digital_output_level Digital output level, number
|
||||
# This is the volume control 0.0 to 1.0 for digital playback to fldigi, etc.
|
||||
# Changes are immediate (no need to restart).
|
||||
digital_output_level = 0.7
|
||||
digital_output_level = 0.700
|
||||
|
||||
## file_play_level File play level, number
|
||||
# Use this to reduce the volume when playing files. For example, if the volume of the CQ
|
||||
# message is too loud, reduce the level relative to radio sound.
|
||||
# Changes are immediate (no need to restart).
|
||||
file_play_level = 1.000
|
||||
#file_play_level = 0.500
|
||||
#file_play_level = 0.100
|
||||
|
||||
# Sound card names:
|
||||
#
|
||||
@@ -1279,6 +1287,18 @@ dc_remove_bw = 100
|
||||
#dc_remove_bw = 200
|
||||
#dc_remove_bw = 400
|
||||
|
||||
## fixed_tune_offset Fixed tune offset, integer
|
||||
# If this is zero, Quisk tunes as usual. The hardware frequency (the VFO or center frequency)
|
||||
# is fixed and Quisk tunes
|
||||
# within the spectrum returned by the hardware. Otherwise enter a fixed offset between the
|
||||
# center frequency and the tuning frequency. Then tuning is done by the hardware.
|
||||
# This is useful with Softrock hardware as
|
||||
# it simplifies the corrections for amplitude and phase imbalance.
|
||||
# Changes are immediate (no need to restart).
|
||||
fixed_tune_offset = 0
|
||||
#fixed_tune_offset = 5000
|
||||
#fixed_tune_offset = 10000
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1908,6 +1928,8 @@ btn_text_switch = u"\u21C4" # Character to switch left-right
|
||||
color_scheme = 'A'
|
||||
#color_scheme = 'B'
|
||||
#color_scheme = 'C'
|
||||
#color_scheme = 'DK1MI_GR'
|
||||
#color_scheme = 'DK1MI_AM'
|
||||
|
||||
## waterfall_palette Waterfall colors, text choice
|
||||
# This controls the colors used in the waterfall. The default color scheme is A, and you can change this scheme
|
||||
@@ -1915,6 +1937,7 @@ color_scheme = 'A'
|
||||
waterfall_palette = 'A'
|
||||
#waterfall_palette = 'B'
|
||||
#waterfall_palette = 'C'
|
||||
#waterfall_palette = 'DK1MI_AM'
|
||||
|
||||
|
||||
|
||||
@@ -1949,6 +1972,8 @@ color_txline = 'red' # vertical line color for tx in graph
|
||||
color_rxline = 'green' # vertical line color for rx in graph
|
||||
color_graph_msg_fg = 'black' # text messages on the graph screen
|
||||
color_graph_msg_bg = 'lemonchiffon2' # background of text messages on the graph screen
|
||||
color_onoff = '#77DD77' # on/off power button
|
||||
color_onoff_text = 'black' # on/off power button text
|
||||
|
||||
# This color scheme B, a dark color scheme designed by Steve Murphy, KB8RWQ.
|
||||
# Additional colors added by N2ADR.
|
||||
@@ -1978,6 +2003,8 @@ color_scheme_B = {
|
||||
'color_rxline' : 'green',
|
||||
'color_graph_msg_fg' : 'white',
|
||||
'color_graph_msg_bg' : '#111111',
|
||||
'color_onoff' : '#77DD77',
|
||||
'color_onoff_text' : 'black',
|
||||
}
|
||||
|
||||
# This is color scheme C:
|
||||
@@ -2015,12 +2042,73 @@ color_scheme_C = {
|
||||
'color_rxline' : '#3CC918',
|
||||
'color_graph_msg_fg' : '#000000',
|
||||
'color_graph_msg_bg' : 'lemonchiffon2',
|
||||
'color_onoff' : '#77DD77',
|
||||
'color_onoff_text' : 'black',
|
||||
}
|
||||
#############################################################################################
|
||||
# This is a green dark color scheme designed by Michael
|
||||
color_scheme_DK1MI_GR = {
|
||||
'color_bg' : '#0F0F0F', # Lower screen background
|
||||
'color_bg_txt' : '#00CC00', # Lower screen text color
|
||||
'color_graph' : '#0F0F0F', # Graph background
|
||||
'color_config2' : '#008000', # color in tab row of config screen
|
||||
'color_gl' : '#2F4F4F', # Lines on the graph
|
||||
'color_graphticks' : '#CCCCCC', # Graph ticks
|
||||
'color_graphline' : '#00FF00', # graph data line color
|
||||
'color_graphlabels' : '#00FF00', # graph label color
|
||||
'color_btn' : '#141414', # button color
|
||||
'color_check_btn' : '#005500', # color of a check button when it is checked
|
||||
'color_cycle_btn' : '#005500', # color of a cycle button when it is checked
|
||||
'color_adjust_btn' : '#005500', # color of an adjustable button when it is checked
|
||||
'color_test' : '#005500', # color of a button used for test (turn off for tx)
|
||||
'color_freq' : '#208B57', # background color of frequency and s-meter
|
||||
'color_freq_txt' : '#00FF00', # text color of frequency display
|
||||
'color_entry' : '#2E2E2E', # frequency entry box
|
||||
'color_entry_txt' : '#00FF00', # text color of entry box
|
||||
'color_enable' : '#00CC00', # text color for an enabled button
|
||||
'color_disable' : '#FF0000', # text color for a disabled button
|
||||
'color_popchoice' : '#555555', # text color for button that pops up a row of buttons
|
||||
'color_bandwidth' : '#003300', # color for bandwidth display; thanks to WB4JFI
|
||||
'color_txline' : '#00CC00', # vertical line color for tx in graph
|
||||
'color_rxline' : '#00FF00', # vertical line color for rx in graph
|
||||
'color_graph_msg_fg' : '#008800', # text messages on the graph screen
|
||||
'color_graph_msg_bg' : '#1E1E1E', # background of text messages on the graph screen
|
||||
'color_onoff' : '#005500', # On/Off button
|
||||
'color_onoff_text' : '#FF0000', # On/Off text
|
||||
}
|
||||
#############################################################################################
|
||||
# This is an amber dark color scheme designed by Michael
|
||||
color_scheme_DK1MI_AM = {
|
||||
'color_bg' : '#0F0F0F', # Lower screen background
|
||||
'color_bg_txt' : '#FFBF00', # Lower screen text color
|
||||
'color_graph' : '#0F0F0F', # Graph background
|
||||
'color_config2' : '#FF8000', # Color in tab row of config screen
|
||||
'color_gl' : '#2F4F4F', # Lines on the graph
|
||||
'color_graphticks' : '#CCCCCC', # Graph ticks
|
||||
'color_graphline' : '#FFBF00', # Graph data line color
|
||||
'color_graphlabels' : '#FFBF00', # Graph label color
|
||||
'color_btn' : '#141414', # Button color
|
||||
'color_check_btn' : '#865E2E', # Color of a check button when it is checked
|
||||
'color_cycle_btn' : '#865E2E', # Color of a cycle button when it is checked
|
||||
'color_adjust_btn' : '#865E2E', # Color of an adjustable button when it is checked
|
||||
'color_test' : '#865E2E', # Color of a button used for test (turn off for tx)
|
||||
'color_freq' : '#FF9933', # Background color of frequency and s-meter
|
||||
'color_freq_txt' : '#000000', # Text color of frequency display
|
||||
'color_entry' : '#2E2E2E', # Frequency entry box
|
||||
'color_entry_txt' : '#FFBF00', # Text color of entry box
|
||||
'color_enable' : '#FFBF00', # Text color for an enabled button
|
||||
'color_disable' : '#FF0000', # Text color for a disabled button
|
||||
'color_popchoice' : '#555555', # Text color for button that pops up a row of buttons
|
||||
'color_bandwidth' : '#4B2E0F', # Color for bandwidth display
|
||||
'color_txline' : '#FFBF00', # Vertical line color for tx in graph
|
||||
'color_rxline' : '#FF8000', # Vertical line color for rx in graph
|
||||
'color_graph_msg_fg' : '#FF8000', # Text messages on the graph screen
|
||||
'color_graph_msg_bg' : '#1E1E1E', # Background of text messages on the graph screen
|
||||
'color_onoff' : '#865E2E', # On/Off button
|
||||
'color_onoff_text' : '#FF0000', # On/Off text
|
||||
}
|
||||
|
||||
|
||||
# These are the palettes for the waterfall. The one used is named waterfallPallette,
|
||||
# so to use a different one, overwrite this name in your configuration file.
|
||||
# These are the palettes for the waterfall.
|
||||
waterfallPalette = (
|
||||
( 0, 0, 0, 0),
|
||||
( 36, 85, 0, 255),
|
||||
@@ -2078,6 +2166,18 @@ waterfallPaletteC = ( # from David Fainitski
|
||||
(255, 245, 99, 3)
|
||||
)
|
||||
|
||||
waterfallPaletteDK1MI_AM = ( # from Michael
|
||||
(0, 0, 0, 0),
|
||||
(32, 25, 20, 0),
|
||||
(64, 58, 40, 6),
|
||||
(96, 78, 50, 16),
|
||||
(128, 120, 85, 29),
|
||||
(160, 144, 100, 51),
|
||||
(192, 195, 140, 43),
|
||||
(224, 198, 150, 35),
|
||||
(255, 255, 191, 0)
|
||||
)
|
||||
|
||||
# This is the data used to draw colored lines on the frequency X axis to
|
||||
# indicate CW and Phone sub-bands. You can make it anything you want.
|
||||
# These are the colors used for sub-bands:
|
||||
@@ -2103,6 +2203,26 @@ RxOnly = '#AAAAAA'
|
||||
# Colors start at the indicated frequency and continue until the
|
||||
# next frequency. The special color "None" turns off color.
|
||||
#
|
||||
BandPlanColors = [ # Display name and corresponding color
|
||||
['CW, General', CW],
|
||||
['CW, Extra', eCW],
|
||||
['Phone, General', Phone],
|
||||
['Phone, Extra', ePhone],
|
||||
['AM', AM],
|
||||
['Data', Data],
|
||||
['DxData', DxData],
|
||||
['RTTY', RTTY],
|
||||
['SSTV', SSTV],
|
||||
['Packet', Packet],
|
||||
['Beacons', Beacons],
|
||||
['Satellite', Satellite],
|
||||
['Repeater out', Repeater],
|
||||
['Repeater in', RepInput],
|
||||
['Simplex', Simplex],
|
||||
['Rx only', RxOnly],
|
||||
['Special', Special],
|
||||
['Other', Other]
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -29,9 +29,10 @@ class Hardware(BaseHardware):
|
||||
# self.hamlib_rigctld_port = 4532 # Standard rigctld control port
|
||||
# self.hamlib_poll_seconds = 0.2 # Time interval to poll for changes
|
||||
def open(self):
|
||||
# Thanks to Peter, DC6AR, Rig ID changed from 229 to 2045.
|
||||
ret = BaseHardware.open(self)
|
||||
if not self.hamlib_connected: # rigctld is not started. Try to start it.
|
||||
os.system("rigctld -m 229 -r /dev/ttyUSB0 -s 4800 & ") # Check the baud rate menu setting
|
||||
os.system("rigctld -m 2045 -r /dev/ttyUSB0 -s 4800 & ") # Check the baud rate menu setting
|
||||
# If this fails, start rigctld by hand.
|
||||
return ret
|
||||
|
||||
|
||||
@@ -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.32'
|
||||
Version = '4.2.38'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
@@ -39,8 +39,10 @@ if sys.platform == "darwin": # Build for Macintosh
|
||||
libraries = ['portaudio', 'fftw3', 'm']
|
||||
if os.path.isdir('/opt/local/include'): # MacPorts
|
||||
base_dir = '/opt/local'
|
||||
elif os.path.isdir('/usr/local/include'): # HomeBrew
|
||||
elif os.path.isdir('/usr/local/include'): # HomeBrew on macOS Intel
|
||||
base_dir = '/usr/local'
|
||||
elif os.path.isdir('/opt/homebrew/include'): # HomeBrew on Apple Silicon
|
||||
base_dir = '/opt/homebrew'
|
||||
else: # Regular build?
|
||||
base_dir = '/usr'
|
||||
if os.path.isfile(base_dir + "/include/pulse/pulseaudio.h"):
|
||||
@@ -83,7 +85,7 @@ N1MM+ and software that uses Hamlib.
|
||||
url = 'http://james.ahlstrom.name/quisk/',
|
||||
packages = ['quisk', 'quisk.n2adr', 'quisk.softrock', 'quisk.freedvpkg',
|
||||
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg',
|
||||
'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd'],
|
||||
'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd', 'quisk.multuspkg'],
|
||||
package_dir = {'quisk' : '.'},
|
||||
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll']},
|
||||
entry_points = {'gui_scripts' : ['quisk = quisk.quisk:main', 'quisk_vna = quisk.quisk_vna:main']},
|
||||
|
||||
@@ -178,6 +178,8 @@ class Hardware(BaseHardware):
|
||||
vfo = self.vfo + int(offset * 1000) # Convert kHz to Hz
|
||||
self.ChangeFrequency(vfo, vfo, 'repeater')
|
||||
return False
|
||||
def ImmediateChange(self, name):
|
||||
return
|
||||
def OnSpot(self, level):
|
||||
pass
|
||||
def HeartBeat(self): # Called at about 10 Hz by the main
|
||||
|
||||
@@ -86,7 +86,9 @@ static struct wav_file file_rec_audio, file_rec_samples, file_rec_mic;
|
||||
static int close_file_rec;
|
||||
|
||||
double digital_output_level = 0.7;
|
||||
static double file_play_level = 1.0;
|
||||
static int dc_remove_bw=100; // bandwidth of DC removal filter
|
||||
static int multus_cw_samples = 0; // always send CW Tx samples to the Multus radio, even in Rx
|
||||
|
||||
static ty_sample_start pt_sample_start;
|
||||
static ty_sample_stop pt_sample_stop;
|
||||
@@ -175,9 +177,10 @@ static void correct_sample (struct sound_dev * dev, complex double * cSamples, i
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
re = creal(cSamples[i]);
|
||||
im = cimag(cSamples[i]);
|
||||
re = re * dev->AmPhAAAA;
|
||||
im = re * dev->AmPhCCCC + im * dev->AmPhDDDD;
|
||||
cSamples[i] = re + I * im;
|
||||
cSamples[i] = re * dev->AmPhAAAA + I * (re * dev->AmPhCCCC + im * dev->AmPhDDDD);
|
||||
//re = re * dev->AmPhAAAA;
|
||||
//im = re * dev->AmPhCCCC + im * dev->AmPhDDDD;
|
||||
//cSamples[i] = re + I * im;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,8 +733,11 @@ void * quisk_make_txIQ(struct sound_dev * dev, int rewind)
|
||||
}
|
||||
// amplitude and phase corrections
|
||||
if (dev->doAmplPhase) {
|
||||
dSamp0 = dSamp0 * dev->AmPhAAAA;
|
||||
dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||
//dSamp0 = dSamp0 * dev->AmPhAAAA;
|
||||
//dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||
d = dSamp0;
|
||||
dSamp0 = d * dev->AmPhAAAA;
|
||||
dSamp1 = d * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||
}
|
||||
switch (dev->sound_format) {
|
||||
case Int16:
|
||||
@@ -836,6 +842,34 @@ int quisk_play_sidetone(struct sound_dev * dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void testing(double complex * cSamples, int nSamples)
|
||||
{
|
||||
int i;
|
||||
static double AvgR=0, AvgQ=0, AvgRQ=0;
|
||||
double d, p, Gain, Phase;
|
||||
time_t tim;
|
||||
static time_t time0;
|
||||
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
d = creal(cSamples[i]);
|
||||
AvgR += d * d;
|
||||
p = cimag(cSamples[i]);
|
||||
AvgQ += p * p;
|
||||
AvgRQ += d * p;
|
||||
}
|
||||
tim = time(NULL);
|
||||
if (tim - time0 >= 2) {
|
||||
time0 = tim;
|
||||
Gain = sqrt(AvgR / AvgQ);
|
||||
Phase = AvgRQ / AvgQ / Gain;
|
||||
Phase = asin(Phase);
|
||||
printf("Time domain Gain %10.7lf, Phase %10.7lf, %10.7lf degrees\n", Gain, Phase, Phase * 57.29578);
|
||||
AvgR = AvgQ = AvgRQ = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int quisk_read_sound(void) // Called from sound thread
|
||||
{ // called in an infinite loop by the main program
|
||||
int i, nSamples, mic_count, mic_interp, retval, is_cw, mic_sample_rate;
|
||||
@@ -913,9 +947,10 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
//QuiskPrintTime("quisk_read_sound end", 0);
|
||||
if (Capture.channel_Delay >= 0) // delay the I or Q channel by one sample
|
||||
delay_sample(&Capture, (double *)cSamples, nSamples);
|
||||
DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state);
|
||||
//testing(cSamples, nSamples);
|
||||
if (Capture.doAmplPhase) // amplitude and phase corrections
|
||||
correct_sample(&Capture, cSamples, nSamples);
|
||||
DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state);
|
||||
if (nSamples <= 0)
|
||||
QuiskSleepMicrosec(2000);
|
||||
#if DEBUG_IO > 1
|
||||
@@ -969,7 +1004,7 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
if (quisk_record_state == TMP_PLAY_SPKR_MIC)
|
||||
quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound
|
||||
else if (quisk_record_state == FILE_PLAY_SPKR_MIC)
|
||||
quisk_file_playback(cSamples, nSamples, 1.0); // replace radio sound
|
||||
quisk_file_playback(cSamples, nSamples, file_play_level); // replace radio sound
|
||||
// Play the demodulated audio
|
||||
#if DEBUG_MIC != 2
|
||||
if ( ! quisk_play_sidetone(&quisk_Playback)) // play sidetone
|
||||
@@ -1075,7 +1110,7 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
if (is_cw) { // Transmit CW; use capture device for timing, not microphone
|
||||
cwCount += (double)retval * quisk_MicPlayback.sample_rate / quisk_sound_state.sample_rate;
|
||||
mic_count = 0;
|
||||
if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0) {
|
||||
if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0 || multus_cw_samples) {
|
||||
while (cwCount >= 1.0) {
|
||||
if (cwEnvelope < 1.0) {
|
||||
cwEnvelope += 1. / (quisk_MicPlayback.sample_rate * 5e-3); // 5 milliseconds
|
||||
@@ -1401,6 +1436,8 @@ void quisk_open_sound(void) // Called from GUI thread
|
||||
RawSamplePlayback.channel_I = 0;
|
||||
RawSamplePlayback.channel_Q = 1;
|
||||
|
||||
file_play_level = QuiskGetConfigDouble("file_play_level", 1.0);
|
||||
|
||||
set_num_channels (&Capture);
|
||||
set_num_channels (&quisk_Playback);
|
||||
set_num_channels (&MicCapture);
|
||||
@@ -1565,10 +1602,9 @@ PyObject * quisk_micplay_channels(PyObject * self, PyObject * args) // Called fr
|
||||
PyObject * quisk_set_sparams(PyObject * self, PyObject * args, PyObject * keywds)
|
||||
{ /* Call with keyword arguments ONLY; change local parameters */
|
||||
static char * kwlist[] = {"dc_remove_bw", "digital_output_level", "remote_control_slave", "remote_control_head",
|
||||
NULL} ;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idii", kwlist, &dc_remove_bw, &digital_output_level,
|
||||
&remote_control_slave, &remote_control_head))
|
||||
"file_play_level", "multus_cw_samples", NULL} ;
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idiidi", kwlist, &dc_remove_bw, &digital_output_level,
|
||||
&remote_control_slave, &remote_control_head, &file_play_level, &multus_cw_samples))
|
||||
return NULL;
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
|
||||
Reference in New Issue
Block a user