Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c9f81254b | ||
|
|
76208643a0 | ||
|
|
31ecbce146 |
@@ -1,3 +1,28 @@
|
|||||||
|
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
|
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
|
Added the get_level Hamlib command "l STRENGTH". It returns the S-meter reading. I finally found some Hamlib
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
#Quisk version 4.2.33
|
#Quisk version 4.2.36
|
||||||
from .quisk import main
|
from .quisk import main
|
||||||
|
|||||||
+405
-198
@@ -1,13 +1,9 @@
|
|||||||
|
import sys, wx, wx.lib, os, re, pickle, traceback, json, copy
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
|
|
||||||
import sys, wx, wx.lib, os, re, pickle, traceback, json
|
|
||||||
# Quisk will alter quisk_conf_defaults to include the user's config file.
|
# Quisk will alter quisk_conf_defaults to include the user's config file.
|
||||||
import quisk_conf_defaults as conf
|
import quisk_conf_defaults as conf
|
||||||
import _quisk as QS
|
import _quisk as QS
|
||||||
from quisk_widgets import QuiskPushbutton, QuiskCheckbutton, QuiskBitField, SliderBoxH, SliderBoxHH
|
from quisk_widgets import QuiskPushbutton, QuiskCheckbutton, QuiskBitField, SliderBoxH, SliderBoxHH
|
||||||
|
from quisk_widgets import FreqFormatter
|
||||||
from quisk_widgets import wxVersion
|
from quisk_widgets import wxVersion
|
||||||
if wxVersion in ('2', '3'):
|
if wxVersion in ('2', '3'):
|
||||||
import wx.combo as wxcombo
|
import wx.combo as wxcombo
|
||||||
@@ -170,12 +166,16 @@ class Configuration:
|
|||||||
radio_dict["Hermes_BandDictTx"] = {}
|
radio_dict["Hermes_BandDictTx"] = {}
|
||||||
def UpdateGlobals(self):
|
def UpdateGlobals(self):
|
||||||
self.RadioName = Settings[1]
|
self.RadioName = Settings[1]
|
||||||
|
application.BandPlanC2T = {}
|
||||||
if self.RadioName == "ConfigFileRadio":
|
if self.RadioName == "ConfigFileRadio":
|
||||||
application.BandPlan = conf.BandPlan
|
application.BandPlan = conf.BandPlan
|
||||||
|
for mode, color in conf.BandPlanColors:
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
elif "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
elif "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
||||||
mode_dict = {"End":None}
|
mode_dict = {"End":None}
|
||||||
for mode, color in Settings[4]["BandPlanColors"]:
|
for mode, color in Settings[4]["BandPlanColors"]:
|
||||||
mode_dict[mode] = color
|
mode_dict[mode] = color
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
plan = []
|
plan = []
|
||||||
for freq, mode in Settings[4]["BandPlan"]:
|
for freq, mode in Settings[4]["BandPlan"]:
|
||||||
freq = int(float(freq) * 1E6 + 0.1)
|
freq = int(float(freq) * 1E6 + 0.1)
|
||||||
@@ -188,6 +188,8 @@ class Configuration:
|
|||||||
application.BandPlan = plan
|
application.BandPlan = plan
|
||||||
else:
|
else:
|
||||||
application.BandPlan = conf.BandPlan
|
application.BandPlan = conf.BandPlan
|
||||||
|
for mode, color in conf.BandPlanColors:
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
if "MidiNoteDict" not in Settings[4]:
|
if "MidiNoteDict" not in Settings[4]:
|
||||||
Settings[4]["MidiNoteDict"] = {}
|
Settings[4]["MidiNoteDict"] = {}
|
||||||
self.settings_changed = True
|
self.settings_changed = True
|
||||||
@@ -282,10 +284,8 @@ class Configuration:
|
|||||||
self.settings_changed = True
|
self.settings_changed = True
|
||||||
errors = errors + "Failed to set %s to %s using format %s\n" % (k, v, fmt)
|
errors = errors + "Failed to set %s to %s using format %s\n" % (k, v, fmt)
|
||||||
#traceback.print_exc()
|
#traceback.print_exc()
|
||||||
if conf.color_scheme == 'B':
|
if conf.color_scheme != 'A':
|
||||||
conf.__dict__.update(conf.color_scheme_B)
|
conf.__dict__.update(getattr(conf, 'color_scheme_' + conf.color_scheme))
|
||||||
elif conf.color_scheme == 'C':
|
|
||||||
conf.__dict__.update(conf.color_scheme_C)
|
|
||||||
self.RequiredValues(radio_dict) # Why not update conf too??? This only updates the radio_dict.
|
self.RequiredValues(radio_dict) # Why not update conf too??? This only updates the radio_dict.
|
||||||
if errors:
|
if errors:
|
||||||
dlg = wx.MessageDialog(None, errors,
|
dlg = wx.MessageDialog(None, errors,
|
||||||
@@ -599,7 +599,7 @@ class Configuration:
|
|||||||
section = None
|
section = None
|
||||||
data_name = None
|
data_name = None
|
||||||
multi_line = False
|
multi_line = False
|
||||||
fp = open(filename, "r")
|
fp = open(filename, "r", encoding="utf8")
|
||||||
for line in fp:
|
for line in fp:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
@@ -829,12 +829,10 @@ class QPowerMeterCalibration(wx.Frame):
|
|||||||
|
|
||||||
## Note: The amplitude/phase adjustments have ideas provided by Andrew Nilsson, VK6JBL.
|
## Note: The amplitude/phase adjustments have ideas provided by Andrew Nilsson, VK6JBL.
|
||||||
## October 2020: changed to make RxTx frequency and VFO two independent variables.
|
## 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):
|
class QAdjustPhase(wx.Frame):
|
||||||
"""Create a window with amplitude and phase adjustment controls"""
|
"""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):
|
def __init__(self, parent, width, rx_tx):
|
||||||
self.width = width
|
|
||||||
self.rx_tx = rx_tx # Must be "rx" or "tx"
|
self.rx_tx = rx_tx # Must be "rx" or "tx"
|
||||||
if rx_tx == 'tx':
|
if rx_tx == 'tx':
|
||||||
self.is_tx = 1
|
self.is_tx = 1
|
||||||
@@ -842,121 +840,232 @@ class QAdjustPhase(wx.Frame):
|
|||||||
else:
|
else:
|
||||||
self.is_tx = 0
|
self.is_tx = 0
|
||||||
t = "Adjust Sound Card Receive Amplitude and Phase"
|
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.panel = wx.Panel(self)
|
||||||
self.MakeControls()
|
self.MakeControls()
|
||||||
self.Redraw()
|
self.Redraw()
|
||||||
self.Show()
|
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
|
def MakeControls(self): # Make controls for phase/amplitude adjustment
|
||||||
panel = self.panel
|
panel = self.panel
|
||||||
width = self.width
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
self.old_amplitude, self.old_phase = application.GetAmplPhase(self.rx_tx)
|
panel.SetSizer(sizer)
|
||||||
self.new_amplitude, self.new_phase = self.old_amplitude, self.old_phase
|
sl_max = application.screen_width * 4 // 10 # maximum +/- value for slider
|
||||||
sl_max = width * 4 // 10 # maximum +/- value for slider
|
|
||||||
self.ampl_scale = float(conf.rx_max_amplitude_correct) / sl_max
|
self.ampl_scale = float(conf.rx_max_amplitude_correct) / sl_max
|
||||||
self.phase_scale = float(conf.rx_max_phase_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)
|
wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
|
||||||
self.SetFont(font)
|
self.SetFont(main_font)
|
||||||
|
panel.SetFont(main_font)
|
||||||
charx = self.GetCharWidth()
|
charx = self.GetCharWidth()
|
||||||
self.tab1 = tab1 = charx
|
|
||||||
chary = self.GetCharHeight()
|
chary = self.GetCharHeight()
|
||||||
deltay = chary * 12 // 10
|
# Create the grid heading and Destroy button
|
||||||
y = chary * 3 // 10
|
self.cell_amph = wx.StaticText(panel, -1, "")
|
||||||
self.controls = []
|
font = wx.Font(conf.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
|
||||||
txt = wx.StaticText(panel, -1, "No data", pos=(tab1, y))
|
wx.FONTWEIGHT_BOLD, False, conf.quisk_typeface)
|
||||||
self.controls.append(txt)
|
self.cell_amph.SetFont(font)
|
||||||
y += txt.GetSize().GetHeight()
|
self.cell_amph.SetLabel("Cell frequency %s, gain %.3f, phase %.3f\u00B0 " % (FreqFormatter(144000000), -1.123, -12.456))
|
||||||
#y += deltay * 14 // 10
|
dv = wx.lib.buttons.GenButton(panel, label=" Destroy ")
|
||||||
self.t_ampl = wx.StaticText(panel, -1, self.f_ampl % self.old_amplitude, pos=(tab1, y))
|
dv.SetBezelWidth(3)
|
||||||
self.controls.append(self.t_ampl)
|
dv.SetBackgroundColour("#DDD")
|
||||||
y += deltay
|
dv.Bind(wx.EVT_BUTTON, self.OnBtnDestroy)
|
||||||
fine = wx.StaticText(panel, -1, 'Fine', pos=(tab1, y))
|
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
self.controls.append(fine)
|
sizer.Add(hbox, flag=wx.BOTTOM|wx.EXPAND, border=chary * 3 // 10)
|
||||||
coarse = wx.StaticText(panel, -1, 'Coarse', pos=(tab1, y + deltay))
|
hbox.Add(self.cell_amph, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=chary // 2)
|
||||||
self.controls.append(coarse)
|
hbox.Add(dv, flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=charx * 5)
|
||||||
tab2 = tab1 + coarse.GetSize().GetWidth() + charx
|
# Create the grid
|
||||||
sliderX = width - tab2 - charx
|
self.grid = wx.grid.Grid(panel, style=wx.BORDER_SIMPLE)
|
||||||
self.ampl1 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
self.grid.CreateGrid(4, 3)
|
||||||
self.controls.append(self.ampl1)
|
self.grid.HideRowLabels()
|
||||||
y += deltay
|
self.selected_row = 0
|
||||||
self.ampl2 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
self.selected_col = 0
|
||||||
self.controls.append(self.ampl2)
|
self.cell_bg_color = self.grid.GetDefaultCellBackgroundColour()
|
||||||
y += deltay * 14 // 10
|
self.grid.SetDefaultCellFont(main_font)
|
||||||
self.PosAmpl(self.old_amplitude)
|
width, h = self.GetTextExtent("987-24 000")
|
||||||
self.t_phase = wx.StaticText(panel, -1, self.f_phase % self.old_phase, pos=(tab1, y))
|
self.grid.SetDefaultColSize(width, True)
|
||||||
self.controls.append(self.t_phase)
|
self.grid.EnableEditing(False)
|
||||||
y += deltay
|
self.grid.SetColLabelValue(0, "Band")
|
||||||
fine = wx.StaticText(panel, -1, 'Fine', pos=(tab1, y))
|
self.grid.SetColLabelValue(1, "VFO")
|
||||||
self.controls.append(fine)
|
self.grid.SetColLabelValue(2, "Tune")
|
||||||
coarse = wx.StaticText(panel, -1, 'Coarse', pos=(tab1, y + deltay))
|
self.grid.SetColSize(0, width // 2)
|
||||||
self.controls.append(coarse)
|
self.grid.SetColSize(1, width * 14 // 10)
|
||||||
self.phase1 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
num_tune = (self.client_width - width * 2) // width
|
||||||
self.controls.append(self.phase1)
|
#print ("num_tune", num_tune)
|
||||||
y += deltay
|
for col in range(3, num_tune + 2):
|
||||||
self.phase2 = wx.Slider(panel, -1, 0, -sl_max, sl_max, pos=(tab2, y), size=(sliderX, -1))
|
self.grid.AppendCols()
|
||||||
self.controls.append(self.phase2)
|
self.grid.SetColLabelValue(col, "Tune")
|
||||||
y += deltay
|
#self.grid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnGridLeftClick)
|
||||||
sv = QuiskPushbutton(panel, self.OnBtnSave, 'Save')
|
self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridSelectCell)
|
||||||
self.controls.append(sv)
|
gbox = wx.BoxSizer(wx.VERTICAL)
|
||||||
dv = QuiskPushbutton(panel, self.OnBtnDestroyVFO, 'Destroy VFO')
|
sizer.Add(gbox, proportion=1, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=charx)
|
||||||
self.controls.append(dv)
|
gbox.Add(self.grid, proportion=1)
|
||||||
ds = QuiskPushbutton(panel, self.OnBtnDestroyALL, 'Destroy ALL')
|
# Create the New Cell text, Manual/Measure, AddCell button
|
||||||
self.controls.append(ds)
|
sbs = wx.StaticBoxSizer(wx.VERTICAL, panel, " New Cell")
|
||||||
cn = QuiskPushbutton(panel, self.OnBtnFinished, 'Finished')
|
sizer.Add(sbs, flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT|wx.BOTTOM, border=charx*3)
|
||||||
self.controls.append(cn)
|
self.add_cell_text = wx.StaticText(panel, -1, "Band")
|
||||||
hl = QuiskPushbutton(panel, self.OnBtnHelp, 'Help')
|
self.add_cell_text.SetFont(main_font)
|
||||||
self.controls.append(hl)
|
sbs.Add(self.add_cell_text, flag=wx.LEFT|wx.TOP, border=charx)
|
||||||
w, h = ds.GetSize().Get()
|
box0 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
sv.SetSize((w, h))
|
sbs.Add(1, charx)
|
||||||
cn.SetSize((w, h))
|
sbs.Add(box0, flag=wx.EXPAND)
|
||||||
hl.SetSize((w, h))
|
rb_manual = wx.RadioButton(panel, -1, "Manual adjustment", style=wx.RB_GROUP)
|
||||||
y += h * 5 // 10
|
self.Bind(wx.EVT_RADIOBUTTON, self.OnBtnManMeasure)
|
||||||
x = (width - w * 5) // 6
|
rb_measure = wx.RadioButton(panel, -1, "Measure")
|
||||||
sv.SetPosition((x, y))
|
if self.is_tx:
|
||||||
dv.SetPosition((x*2 + w, y))
|
rb_manual.Enable(False)
|
||||||
ds.SetPosition((x*3 + w*2, y))
|
rb_measure.Enable(False)
|
||||||
cn.SetPosition((x*4 + w*3, y))
|
b = wx.lib.buttons.GenButton(panel, label=" Add cell ")
|
||||||
hl.SetPosition((x*5 + w*4, y))
|
b.Bind(wx.EVT_BUTTON, self.OnBtnAddCell)
|
||||||
sv.SetBackgroundColour('light blue')
|
b.SetBezelWidth(3)
|
||||||
dv.SetBackgroundColour('light blue')
|
b.SetBackgroundColour("#DDD")
|
||||||
ds.SetBackgroundColour('light blue')
|
box0.Add(rb_manual, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx)
|
||||||
cn.SetBackgroundColour('light blue')
|
box0.Add(rb_measure, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=charx*5)
|
||||||
hl.SetBackgroundColour('light blue')
|
box0.Add(b, flag=wx.LEFT, border=charx*5)
|
||||||
y += h
|
# Create the amplitude slider controls
|
||||||
y += h * 4 // 10
|
box1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
self.PosPhase(self.old_phase)
|
box2 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
self.SetClientSize(wx.Size(width, y))
|
sbs.Add(box1, flag=wx.EXPAND)
|
||||||
self.ampl1.Bind(wx.EVT_SCROLL, self.OnChange)
|
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.ampl2.Bind(wx.EVT_SCROLL, self.OnAmpl2)
|
||||||
self.phase1.Bind(wx.EVT_SCROLL, self.OnChange)
|
# Create the phase slider controls
|
||||||
self.phase2.Bind(wx.EVT_SCROLL, self.OnPhase2)
|
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):
|
def Redraw(self):
|
||||||
# Print available data points
|
self.grid.ClearGrid()
|
||||||
self.band = application.lastBand
|
# Print available data cells
|
||||||
data = application.bandAmplPhase.get(self.band, {})
|
row = 0
|
||||||
data = data.get(self.rx_tx, [])
|
bands = []
|
||||||
if data:
|
self.cell_dict = {}
|
||||||
t = "Band %s VFO and frequency list\n" % self.band
|
for band in self.bandAmplPhase:
|
||||||
for vfo, items in data:
|
if band != "Version" and self.rx_tx in self.bandAmplPhase[band]:
|
||||||
t += "%10d:" % vfo
|
bands.append(band)
|
||||||
for freq, ampl, phase in items:
|
bands.sort(reverse=True)
|
||||||
t += " %7d" % freq
|
for band in bands:
|
||||||
t += "\n"
|
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:
|
else:
|
||||||
t = "Band %s: No data." % self.band
|
self.new_tune, gain, phase = QS.softrock_corrections(2)
|
||||||
x, y = self.controls[0].GetPosition()
|
ampl = gain - 1.0
|
||||||
height = self.controls[0].GetSize().GetHeight()
|
self.new_amplitude = ampl
|
||||||
self.controls[0].Destroy()
|
phase *= 57.2958
|
||||||
txt = wx.StaticText(self.panel, -1, t, pos=(self.tab1, y))
|
self.new_phase = phase
|
||||||
self.controls[0] = txt
|
self.PosAmpl(ampl)
|
||||||
delta = txt.GetSize().GetHeight() - height
|
self.PosPhase(phase)
|
||||||
for ctrl in self.controls[1:]:
|
self.SetNewCellText()
|
||||||
x, y = ctrl.GetPosition()
|
def OnGridSelectCell(self, event=None):
|
||||||
y += delta
|
self.grid.SetCellBackgroundColour(self.selected_row, self.selected_col, self.cell_bg_color)
|
||||||
ctrl.Move(x, y)
|
if event:
|
||||||
h = self.GetClientSize().GetHeight()
|
event.Skip()
|
||||||
self.SetClientSize(wx.Size(self.width, h + delta))
|
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
|
def PosAmpl(self, ampl): # set pos1, pos2 for amplitude
|
||||||
pos2 = round(ampl / self.ampl_scale)
|
pos2 = round(ampl / self.ampl_scale)
|
||||||
remain = ampl - pos2 * self.ampl_scale
|
remain = ampl - pos2 * self.ampl_scale
|
||||||
@@ -967,83 +1076,196 @@ class QAdjustPhase(wx.Frame):
|
|||||||
pos2 = round(phase / self.phase_scale)
|
pos2 = round(phase / self.phase_scale)
|
||||||
remain = phase - pos2 * self.phase_scale
|
remain = phase - pos2 * self.phase_scale
|
||||||
pos1 = round(remain / self.phase_scale * 50.0)
|
pos1 = round(remain / self.phase_scale * 50.0)
|
||||||
self.phase1.SetValue(pos1)
|
self.phas1.SetValue(pos1)
|
||||||
self.phase2.SetValue(pos2)
|
self.phas2.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
|
|
||||||
def OnAmpl2(self, event): # re-center the fine slider when the coarse slider is adjusted
|
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()
|
ampl = self.ampl_scale * self.ampl1.GetValue() / 50.0 + self.ampl_scale * self.ampl2.GetValue()
|
||||||
self.PosAmpl(ampl)
|
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
|
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.PosPhase(phase)
|
||||||
self.OnChange(event)
|
self.OnPhase1(event)
|
||||||
def OnBtnDestroyVFO(self, event): # Remove entry with the application VFO
|
def OnPhase1(self, event):
|
||||||
data = application.bandAmplPhase.get(self.band, {})
|
phase = self.phase_scale * self.phas1.GetValue() / 50.0 + self.phase_scale * self.phas2.GetValue()
|
||||||
data = data.get(self.rx_tx, [])
|
self.new_phase = phase
|
||||||
for i in range(len(data)):
|
self.SetNewCellText()
|
||||||
if data[i][0] == application.VFO:
|
QS.set_ampl_phase(self.new_amplitude, self.new_phase, self.is_tx)
|
||||||
del data[i]
|
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
|
break
|
||||||
|
else:
|
||||||
|
tunes.append([tune, ampl, phase])
|
||||||
|
tunes.sort()
|
||||||
|
self.dirty = True
|
||||||
self.Redraw()
|
self.Redraw()
|
||||||
|
return
|
||||||
|
vfos_tunes.append([vfo, [[tune, ampl, phase]]])
|
||||||
|
vfos_tunes = vfos_tunes.sort()
|
||||||
|
self.dirty = True
|
||||||
|
self.Redraw()
|
||||||
|
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()
|
||||||
|
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):
|
def OnBtnSave(self, event):
|
||||||
data = application.bandAmplPhase
|
application.bandAmplPhase = copy.deepcopy(self.bandAmplPhase)
|
||||||
if self.band not in data:
|
self.dirty = False
|
||||||
data[self.band] = {}
|
def OnBtnExit(self, event):
|
||||||
data = data[self.band]
|
#self.OnGraphData(None)
|
||||||
if self.rx_tx not in data:
|
if self.dirty:
|
||||||
data[self.rx_tx] = []
|
dlg = wx.MessageDialog(self,
|
||||||
data = data[self.rx_tx]
|
"Your changes are not saved. Do you want to save them?", "Changes Were Made",
|
||||||
for vfo, items in data: # All items must be lists, not tuples
|
wx.OK|wx.CANCEL|wx.CANCEL_DEFAULT|wx.ICON_WARNING)
|
||||||
if vfo == application.VFO:
|
dlg.SetOKLabel("Discard Changes")
|
||||||
for i in range(len(items)):
|
ret = dlg.ShowModal()
|
||||||
item = items[i]
|
dlg.Destroy()
|
||||||
if item[0] == application.rxFreq:
|
if ret == wx.ID_CANCEL:
|
||||||
item[1] = self.new_amplitude
|
return
|
||||||
item[2] = self.new_phase
|
QS.softrock_corrections(0)
|
||||||
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()
|
|
||||||
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.Redraw()
|
|
||||||
def OnBtnFinished(self, event=None):
|
|
||||||
ampl, phase = application.GetAmplPhase(self.rx_tx)
|
ampl, phase = application.GetAmplPhase(self.rx_tx)
|
||||||
QS.set_ampl_phase(ampl, phase, self.is_tx)
|
QS.set_ampl_phase(ampl, phase, self.is_tx)
|
||||||
application.w_phase = None
|
application.w_phase = None
|
||||||
self.Destroy()
|
self.Destroy()
|
||||||
def OnBtnHelp(self, event=None):
|
def OnBtnHelp(self, event=None):
|
||||||
dlg = wx.MessageDialog(self,
|
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. \
|
'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 VFO and the frequency as desired. Then adjust the sliders to minimize the image. Press "Save" when satisfied. \
|
Adjust the amplitude and phase sliders to minimize the image. To save the adjustment, press Add Cell. \
|
||||||
To adjust the VFO, use the band Up/Down buttons, or right click the graph at the desired VFO. \
|
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. \
|
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 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)
|
, "Adjustment Help", style=wx.OK)
|
||||||
dlg.ShowModal()
|
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
|
class ListEditDialog(wx.Dialog): # Display a dialog with a List-Edit control, plus Ok/Cancel
|
||||||
def __init__(self, parent, title, choice, choices, width):
|
def __init__(self, parent, title, choice, choices, width):
|
||||||
@@ -2022,6 +2244,9 @@ class ControlMixin:
|
|||||||
elif name == "digital_output_level":
|
elif name == "digital_output_level":
|
||||||
setattr(conf, name, x)
|
setattr(conf, name, x)
|
||||||
QS.set_sparams(digital_output_level=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_':
|
elif name[0:7] == 'hermes_':
|
||||||
if name == 'hermes_TxLNA_dB':
|
if name == 'hermes_TxLNA_dB':
|
||||||
application.Hardware.ChangeTxLNA(x)
|
application.Hardware.ChangeTxLNA(x)
|
||||||
@@ -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.radio_dict = local_conf.GetRadioDict(self.radio_name)
|
||||||
self.num_cols = 8
|
self.num_cols = 8
|
||||||
for name, text, fmt, help_text, values in local_conf.GetSectionData('Sound'):
|
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)
|
value = self.GetValue(name, self.radio_dict)
|
||||||
no_edit = "choice" in fmt or fmt == 'boolean'
|
no_edit = "choice" in fmt or fmt == 'boolean'
|
||||||
txt, cb, btn = self.AddTextComboHelp(1, text, value, values, help_text, no_edit)
|
txt, cb, btn = self.AddTextComboHelp(1, text, value, values, help_text, no_edit)
|
||||||
cb.handler = self.OnChange
|
cb.handler = self.OnChange
|
||||||
cb.quisk_data_name = name
|
cb.quisk_data_name = name
|
||||||
break
|
self.NextRow()
|
||||||
self.NextRow()
|
self.NextRow()
|
||||||
# Add the grid for the sound settings
|
# Add the grid for the sound settings
|
||||||
sizer = wx.GridBagSizer(2, 2)
|
sizer = wx.GridBagSizer(2, 2)
|
||||||
@@ -3984,28 +4209,7 @@ class BandPlanDlg(wx.Dialog, ControlMixin):
|
|||||||
if "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
if "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
||||||
colors = Settings[4]["BandPlanColors"]
|
colors = Settings[4]["BandPlanColors"]
|
||||||
else:
|
else:
|
||||||
Get = conf.__dict__.get # Get colors from the user's config file, if any.
|
colors = conf.BandPlanColors
|
||||||
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)]
|
|
||||||
]
|
|
||||||
self.modes = []
|
self.modes = []
|
||||||
self.color_dict = {None:"End"}
|
self.color_dict = {None:"End"}
|
||||||
self.mode_dict = {"End":None}
|
self.mode_dict = {"End":None}
|
||||||
@@ -4212,6 +4416,9 @@ The mode begins at the start frequency and ends at the next frequency.'
|
|||||||
plan.append([hertz, color])
|
plan.append([hertz, color])
|
||||||
Settings[4]["BandPlan"] = freq_mode
|
Settings[4]["BandPlan"] = freq_mode
|
||||||
application.BandPlan = plan
|
application.BandPlan = plan
|
||||||
|
application.BandPlanC2T = {}
|
||||||
|
for mode, color in mode_color:
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
local_conf.settings_changed = True
|
local_conf.settings_changed = True
|
||||||
self.EndModal(wx.ID_OK)
|
self.EndModal(wx.ID_OK)
|
||||||
def OnCancel(self, event):
|
def OnCancel(self, event):
|
||||||
|
|||||||
@@ -836,13 +836,14 @@ by hand if you are a Python expert. Otherwise you can just read the values.
|
|||||||
<br>
|
<br>
|
||||||
To create a Receive correction point for a given VFO and frequency, attach a signal generator to the SoftRock through an attenuator,
|
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.
|
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
|
Choose "Measure" to measure the corrections and then "Add Cell".
|
||||||
the amplitude and phase sliders on the adjustment screen, and then press "Save".
|
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>
|
||||||
<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,
|
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
|
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>
|
||||||
<br>
|
<br>
|
||||||
</p>
|
</p>
|
||||||
@@ -1468,7 +1469,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
|
Monitor of QuiskDigitalOutput. Then everything should work. You do not need to use pavucontrol again because PulseAudio will
|
||||||
remember the settings.
|
remember the settings.
|
||||||
<br>
|
<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>
|
<br>
|
||||||
If your digital program only uses Alsa devices or you don't want to use PulseAudio,
|
If your digital program only uses Alsa devices or you don't want to use PulseAudio,
|
||||||
you can use the ALSA loopback device.
|
you can use the ALSA loopback device.
|
||||||
|
|||||||
@@ -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_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 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 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 complex double PySampleBuf[SAMP_BUFFER_SIZE]; // buffer for samples returned from Python
|
||||||
static int PySampleCount; // count of samples in buffer
|
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);
|
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||||
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
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_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p4, 2);
|
||||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||||
//quisk_calc_audio_graph(pow(2, 31) - 1, NULL, dsamples, nSamples, 1);
|
//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);
|
return PyInt_FromLong(quisk_serial_ptt);
|
||||||
if (strcmp(name, "hl2_txbuf_errors") == 0)
|
if (strcmp(name, "hl2_txbuf_errors") == 0)
|
||||||
return PyInt_FromLong(hl2_txbuf_errors);
|
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)
|
if (strcmp(name, "quisk_tx_inhibit") == 0)
|
||||||
return PyInt_FromLong((long)quisk_tx_inhibit);
|
return PyInt_FromLong((long)quisk_tx_inhibit);
|
||||||
Py_INCREF (Py_None);
|
Py_INCREF (Py_None);
|
||||||
@@ -3874,6 +3880,10 @@ static int read_rx_udp17(complex double * cSamples0) // Read samples from UDP
|
|||||||
fft_data * ptFFT;
|
fft_data * ptFFT;
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
static int block_number=0;
|
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
|
// Data from the receiver is little-endian
|
||||||
|
|
||||||
if ( ! rx_udp_gain_correct) { // correct for second stage CIC decimation JIM JIM
|
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 (xr & 0x100) { // channel 1
|
||||||
if (quisk_invert_spectrum) // Invert spectrum
|
if (quisk_invert_spectrum) // Invert spectrum
|
||||||
sample = conj(sample);
|
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.
|
// Put samples into the fft input array.
|
||||||
ptFFT = fft_data_array + fft_data_index;
|
ptFFT = fft_data_array + fft_data_index;
|
||||||
if ( ! (xi & 0x100)) { // zero marker for start of first block
|
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;
|
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
|
static PyObject * change_rates(PyObject * self, PyObject * args) // Called from GUI thread
|
||||||
{ // Change to new sample rates
|
{ // Change to new sample rates
|
||||||
|
|
||||||
@@ -5033,6 +5060,135 @@ static PyObject * get_bandscope(PyObject * self, PyObject * args) // Called by t
|
|||||||
return Py_None;
|
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
|
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;
|
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
|
// Continue with FFT calculation
|
||||||
for (i = 0; i < fft_size; i++) // multiply by window
|
for (i = 0; i < fft_size; i++) // multiply by window
|
||||||
ptFft->samples[i] *= fft_window[i];
|
ptFft->samples[i] *= fft_window[i];
|
||||||
|
//check_channel_delay(ptFft);
|
||||||
fftw_execute_dft(quisk_fft_plan, ptFft->samples, ptFft->samples); // Calculate FFT
|
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
|
// Create RMS s-meter value at known bandwidth
|
||||||
// The pass band is (rx_tune_freq + filter_start_offset) to += bandwidth
|
// The pass band is (rx_tune_freq + filter_start_offset) to += bandwidth
|
||||||
// d1 is the tune frequency
|
// 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);
|
//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 {
|
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++;
|
count_fft++;
|
||||||
k = 0;
|
k = 0;
|
||||||
for (i = fft_size / 2; i < fft_size; i++) // Negative frequencies
|
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_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_tx_filter", quisk_get_tx_filter, METH_VARARGS, "Return the frequency response of the transmit filter."},
|
||||||
{"get_audio_graph", get_audio_graph, METH_VARARGS, "Return a tuple of the audio graph data."},
|
{"get_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_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."},
|
{"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."},
|
{"get_overrange", get_overrange, METH_VARARGS, "Return the count of overrange (clip) for the ADC."},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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
|
# This free software is licensed for use under the GNU General Public
|
||||||
# License (GPL), see http://www.opensource.org.
|
# License (GPL), see http://www.opensource.org.
|
||||||
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
|
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
|
||||||
@@ -1892,6 +1892,12 @@ class GraphScreen(wx.Window):
|
|||||||
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
|
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
|
||||||
self.Bind(wx.EVT_MOTION, self.OnMotion)
|
self.Bind(wx.EVT_MOTION, self.OnMotion)
|
||||||
self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
|
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()
|
self.MakeDisplay()
|
||||||
def MakeDisplay(self):
|
def MakeDisplay(self):
|
||||||
self.display = GraphDisplay(self, self.originX, 0, self.graph_width, 5, self.chary)
|
self.display = GraphDisplay(self, self.originX, 0, self.graph_width, 5, self.chary)
|
||||||
@@ -2243,6 +2249,44 @@ class GraphScreen(wx.Window):
|
|||||||
self.ChangeHwFrequency(self.txFreq, self.VFO, 'MouseMotion', event=event, rx_freq=freq2)
|
self.ChangeHwFrequency(self.txFreq, self.VFO, 'MouseMotion', event=event, rx_freq=freq2)
|
||||||
else: # Mouse motion changes the transmit frequency
|
else: # Mouse motion changes the transmit frequency
|
||||||
self.ChangeHwFrequency(self.txFreq + freq, self.VFO, 'MouseMotion', event=event)
|
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):
|
def OnWheel(self, event):
|
||||||
if conf.freq_spacing:
|
if conf.freq_spacing:
|
||||||
wm = conf.freq_spacing
|
wm = conf.freq_spacing
|
||||||
@@ -2493,12 +2537,10 @@ class WaterfallDisplay(wx.Window):
|
|||||||
self.top_key = 8
|
self.top_key = 8
|
||||||
self.top_size = (self.top_key + 2) * (self.top_key - 1) // 2
|
self.top_size = (self.top_key + 2) * (self.top_key - 1) // 2
|
||||||
# Make the palette
|
# Make the palette
|
||||||
if conf.waterfall_palette == 'B':
|
if conf.waterfall_palette == 'A':
|
||||||
pal2 = conf.waterfallPaletteB
|
|
||||||
elif conf.waterfall_palette == 'C':
|
|
||||||
pal2 = conf.waterfallPaletteC
|
|
||||||
else:
|
|
||||||
pal2 = conf.waterfallPalette
|
pal2 = conf.waterfallPalette
|
||||||
|
else:
|
||||||
|
pal2 = getattr(conf, "waterfallPalette" + conf.waterfall_palette)
|
||||||
red = bytearray(256)
|
red = bytearray(256)
|
||||||
green = bytearray(256)
|
green = bytearray(256)
|
||||||
blue = bytearray(256)
|
blue = bytearray(256)
|
||||||
@@ -3584,7 +3626,7 @@ class App(wx.App):
|
|||||||
self.save_time0 = self.timer
|
self.save_time0 = self.timer
|
||||||
self.smeter_db_time0 = self.timer
|
self.smeter_db_time0 = self.timer
|
||||||
self.smeter_sunits_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_index = 0
|
||||||
self.multi_rx_timer = self.timer
|
self.multi_rx_timer = self.timer
|
||||||
self.band_up_down = 0 # Are band Up/Down buttons in use?
|
self.band_up_down = 0 # Are band Up/Down buttons in use?
|
||||||
@@ -3740,6 +3782,7 @@ class App(wx.App):
|
|||||||
pass #traceback.print_exc()
|
pass #traceback.print_exc()
|
||||||
if "Version" not in self.bandAmplPhase:
|
if "Version" not in self.bandAmplPhase:
|
||||||
self.FixAmplPhase() # Convert to new format Oct 2020
|
self.FixAmplPhase() # Convert to new format Oct 2020
|
||||||
|
self.FixAmplPhase2() # New logic June 2024
|
||||||
if Hardware.VarDecimGetChoices(): # Hardware can change the decimation.
|
if Hardware.VarDecimGetChoices(): # Hardware can change the decimation.
|
||||||
self.sample_rate = Hardware.VarDecimSet() # Get the sample rate.
|
self.sample_rate = Hardware.VarDecimSet() # Get the sample rate.
|
||||||
self.vardecim_set = self.sample_rate
|
self.vardecim_set = self.sample_rate
|
||||||
@@ -4406,6 +4449,25 @@ class App(wx.App):
|
|||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.bandAmplPhase[band] = {}
|
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):
|
def ImmediateChange(self, name):
|
||||||
if name == "pulse_audio_verbose_output":
|
if name == "pulse_audio_verbose_output":
|
||||||
value = getattr(conf, name)
|
value = getattr(conf, name)
|
||||||
@@ -4854,7 +4916,8 @@ class App(wx.App):
|
|||||||
self.freqDisplay.Display(self.txFreq + self.VFO)
|
self.freqDisplay.Display(self.txFreq + self.VFO)
|
||||||
# On/Off button
|
# On/Off button
|
||||||
if conf.button_layout == 'Large screen':
|
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)
|
b_onoff.SetValue(True, do_cmd=False)
|
||||||
h = b_freqdisp.height
|
h = b_freqdisp.height
|
||||||
b_onoff.SetSizeHints(h, h, h, h)
|
b_onoff.SetSizeHints(h, h, h, h)
|
||||||
@@ -5839,10 +5902,8 @@ class App(wx.App):
|
|||||||
self.graph.SetVFO(vfo)
|
self.graph.SetVFO(vfo)
|
||||||
self.waterfall.SetVFO(vfo)
|
self.waterfall.SetVFO(vfo)
|
||||||
self.station_screen.Refresh()
|
self.station_screen.Refresh()
|
||||||
if self.w_phase:
|
|
||||||
self.w_phase.Redraw()
|
|
||||||
if change:
|
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')
|
ampl, phase = self.GetAmplPhase('rx')
|
||||||
QS.set_ampl_phase(ampl, phase, 0)
|
QS.set_ampl_phase(ampl, phase, 0)
|
||||||
ampl, phase = self.GetAmplPhase('tx')
|
ampl, phase = self.GetAmplPhase('tx')
|
||||||
@@ -6534,6 +6595,8 @@ class App(wx.App):
|
|||||||
self.old_tx_inhibit = self.tx_inhibit
|
self.old_tx_inhibit = self.tx_inhibit
|
||||||
self.graph.display.Refresh()
|
self.graph.display.Refresh()
|
||||||
self.waterfall.pane1.display.Refresh()
|
self.waterfall.pane1.display.Refresh()
|
||||||
|
if self.tx_inhibit and self.spotButton.GetValue():
|
||||||
|
self.spotButton.SetValue(0, True)
|
||||||
msg = QS.GetQuiskPrintf()
|
msg = QS.GetQuiskPrintf()
|
||||||
if msg:
|
if msg:
|
||||||
print(msg, end='')
|
print(msg, end='')
|
||||||
@@ -6552,9 +6615,10 @@ class App(wx.App):
|
|||||||
self.ChangeDisplayFrequency(tune - vfo, vfo)
|
self.ChangeDisplayFrequency(tune - vfo, vfo)
|
||||||
self.FldigiPoll()
|
self.FldigiPoll()
|
||||||
self.HamlibPoll()
|
self.HamlibPoll()
|
||||||
#if self.timer - self.fewsec_time0 > 3.0:
|
if self.timer - self.slowheart_time0 > 0.5:
|
||||||
# self.fewsec_time0 = self.timer
|
self.slowheart_time0 = self.timer
|
||||||
# print ('fewswc')
|
if self.w_phase:
|
||||||
|
self.w_phase.SlowHeartBeat()
|
||||||
if self.timer - self.save_time0 > 20.0:
|
if self.timer - self.save_time0 > 20.0:
|
||||||
self.save_time0 = self.timer
|
self.save_time0 = self.timer
|
||||||
if self.CheckState():
|
if self.CheckState():
|
||||||
|
|||||||
+112
-4
@@ -1084,7 +1084,15 @@ digital_rx1_name = ""
|
|||||||
## digital_output_level Digital output level, number
|
## digital_output_level Digital output level, number
|
||||||
# This is the volume control 0.0 to 1.0 for digital playback to fldigi, etc.
|
# This is the volume control 0.0 to 1.0 for digital playback to fldigi, etc.
|
||||||
# Changes are immediate (no need to restart).
|
# 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:
|
# Sound card names:
|
||||||
#
|
#
|
||||||
@@ -1908,6 +1916,8 @@ btn_text_switch = u"\u21C4" # Character to switch left-right
|
|||||||
color_scheme = 'A'
|
color_scheme = 'A'
|
||||||
#color_scheme = 'B'
|
#color_scheme = 'B'
|
||||||
#color_scheme = 'C'
|
#color_scheme = 'C'
|
||||||
|
#color_scheme = 'DK1MI_GR'
|
||||||
|
#color_scheme = 'DK1MI_AM'
|
||||||
|
|
||||||
## waterfall_palette Waterfall colors, text choice
|
## 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
|
# This controls the colors used in the waterfall. The default color scheme is A, and you can change this scheme
|
||||||
@@ -1915,6 +1925,7 @@ color_scheme = 'A'
|
|||||||
waterfall_palette = 'A'
|
waterfall_palette = 'A'
|
||||||
#waterfall_palette = 'B'
|
#waterfall_palette = 'B'
|
||||||
#waterfall_palette = 'C'
|
#waterfall_palette = 'C'
|
||||||
|
#waterfall_palette = 'DK1MI_AM'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1949,6 +1960,8 @@ color_txline = 'red' # vertical line color for tx in graph
|
|||||||
color_rxline = 'green' # vertical line color for rx 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_fg = 'black' # text messages on the graph screen
|
||||||
color_graph_msg_bg = 'lemonchiffon2' # background of 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.
|
# This color scheme B, a dark color scheme designed by Steve Murphy, KB8RWQ.
|
||||||
# Additional colors added by N2ADR.
|
# Additional colors added by N2ADR.
|
||||||
@@ -1978,6 +1991,8 @@ color_scheme_B = {
|
|||||||
'color_rxline' : 'green',
|
'color_rxline' : 'green',
|
||||||
'color_graph_msg_fg' : 'white',
|
'color_graph_msg_fg' : 'white',
|
||||||
'color_graph_msg_bg' : '#111111',
|
'color_graph_msg_bg' : '#111111',
|
||||||
|
'color_onoff' : '#77DD77',
|
||||||
|
'color_onoff_text' : 'black',
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is color scheme C:
|
# This is color scheme C:
|
||||||
@@ -2015,12 +2030,73 @@ color_scheme_C = {
|
|||||||
'color_rxline' : '#3CC918',
|
'color_rxline' : '#3CC918',
|
||||||
'color_graph_msg_fg' : '#000000',
|
'color_graph_msg_fg' : '#000000',
|
||||||
'color_graph_msg_bg' : 'lemonchiffon2',
|
'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.
|
||||||
# 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.
|
|
||||||
waterfallPalette = (
|
waterfallPalette = (
|
||||||
( 0, 0, 0, 0),
|
( 0, 0, 0, 0),
|
||||||
( 36, 85, 0, 255),
|
( 36, 85, 0, 255),
|
||||||
@@ -2078,6 +2154,18 @@ waterfallPaletteC = ( # from David Fainitski
|
|||||||
(255, 245, 99, 3)
|
(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
|
# 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.
|
# indicate CW and Phone sub-bands. You can make it anything you want.
|
||||||
# These are the colors used for sub-bands:
|
# These are the colors used for sub-bands:
|
||||||
@@ -2103,6 +2191,26 @@ RxOnly = '#AAAAAA'
|
|||||||
# Colors start at the indicated frequency and continue until the
|
# Colors start at the indicated frequency and continue until the
|
||||||
# next frequency. The special color "None" turns off color.
|
# 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]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import os
|
|||||||
# You must define the version here. A title string including
|
# You must define the version here. A title string including
|
||||||
# the version will be written to __init__.py and read by quisk.py.
|
# the version will be written to __init__.py and read by quisk.py.
|
||||||
|
|
||||||
Version = '4.2.33'
|
Version = '4.2.36'
|
||||||
|
|
||||||
fp = open("__init__.py", "w") # write title string
|
fp = open("__init__.py", "w") # write title string
|
||||||
fp.write("#Quisk version %s\n" % Version)
|
fp.write("#Quisk version %s\n" % Version)
|
||||||
@@ -39,8 +39,10 @@ if sys.platform == "darwin": # Build for Macintosh
|
|||||||
libraries = ['portaudio', 'fftw3', 'm']
|
libraries = ['portaudio', 'fftw3', 'm']
|
||||||
if os.path.isdir('/opt/local/include'): # MacPorts
|
if os.path.isdir('/opt/local/include'): # MacPorts
|
||||||
base_dir = '/opt/local'
|
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'
|
base_dir = '/usr/local'
|
||||||
|
elif os.path.isdir('/opt/homebrew/include'): # HomeBrew on Apple Silicon
|
||||||
|
base_dir = '/opt/homebrew'
|
||||||
else: # Regular build?
|
else: # Regular build?
|
||||||
base_dir = '/usr'
|
base_dir = '/usr'
|
||||||
if os.path.isfile(base_dir + "/include/pulse/pulseaudio.h"):
|
if os.path.isfile(base_dir + "/include/pulse/pulseaudio.h"):
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ static struct wav_file file_rec_audio, file_rec_samples, file_rec_mic;
|
|||||||
static int close_file_rec;
|
static int close_file_rec;
|
||||||
|
|
||||||
double digital_output_level = 0.7;
|
double digital_output_level = 0.7;
|
||||||
|
static double file_play_level = 1.0;
|
||||||
static int dc_remove_bw=100; // bandwidth of DC removal filter
|
static int 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_start pt_sample_start;
|
||||||
static ty_sample_stop pt_sample_stop;
|
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++) {
|
for (i = 0; i < nSamples; i++) {
|
||||||
re = creal(cSamples[i]);
|
re = creal(cSamples[i]);
|
||||||
im = cimag(cSamples[i]);
|
im = cimag(cSamples[i]);
|
||||||
re = re * dev->AmPhAAAA;
|
cSamples[i] = re * dev->AmPhAAAA + I * (re * dev->AmPhCCCC + im * dev->AmPhDDDD);
|
||||||
im = re * dev->AmPhCCCC + im * dev->AmPhDDDD;
|
//re = re * dev->AmPhAAAA;
|
||||||
cSamples[i] = re + I * im;
|
//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
|
// amplitude and phase corrections
|
||||||
if (dev->doAmplPhase) {
|
if (dev->doAmplPhase) {
|
||||||
dSamp0 = dSamp0 * dev->AmPhAAAA;
|
//dSamp0 = dSamp0 * dev->AmPhAAAA;
|
||||||
dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
//dSamp1 = dSamp0 * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||||
|
d = dSamp0;
|
||||||
|
dSamp0 = d * dev->AmPhAAAA;
|
||||||
|
dSamp1 = d * dev->AmPhCCCC + dSamp1 * dev->AmPhDDDD;
|
||||||
}
|
}
|
||||||
switch (dev->sound_format) {
|
switch (dev->sound_format) {
|
||||||
case Int16:
|
case Int16:
|
||||||
@@ -836,6 +842,34 @@ int quisk_play_sidetone(struct sound_dev * dev)
|
|||||||
return 0;
|
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
|
int quisk_read_sound(void) // Called from sound thread
|
||||||
{ // called in an infinite loop by the main program
|
{ // called in an infinite loop by the main program
|
||||||
int i, nSamples, mic_count, mic_interp, retval, is_cw, mic_sample_rate;
|
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);
|
//QuiskPrintTime("quisk_read_sound end", 0);
|
||||||
if (Capture.channel_Delay >= 0) // delay the I or Q channel by one sample
|
if (Capture.channel_Delay >= 0) // delay the I or Q channel by one sample
|
||||||
delay_sample(&Capture, (double *)cSamples, nSamples);
|
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
|
if (Capture.doAmplPhase) // amplitude and phase corrections
|
||||||
correct_sample(&Capture, cSamples, nSamples);
|
correct_sample(&Capture, cSamples, nSamples);
|
||||||
DCremove(cSamples, nSamples, quisk_sound_state.sample_rate, key_state);
|
|
||||||
if (nSamples <= 0)
|
if (nSamples <= 0)
|
||||||
QuiskSleepMicrosec(2000);
|
QuiskSleepMicrosec(2000);
|
||||||
#if DEBUG_IO > 1
|
#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)
|
if (quisk_record_state == TMP_PLAY_SPKR_MIC)
|
||||||
quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound
|
quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound
|
||||||
else if (quisk_record_state == FILE_PLAY_SPKR_MIC)
|
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
|
// Play the demodulated audio
|
||||||
#if DEBUG_MIC != 2
|
#if DEBUG_MIC != 2
|
||||||
if ( ! quisk_play_sidetone(&quisk_Playback)) // play sidetone
|
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
|
if (is_cw) { // Transmit CW; use capture device for timing, not microphone
|
||||||
cwCount += (double)retval * quisk_MicPlayback.sample_rate / quisk_sound_state.sample_rate;
|
cwCount += (double)retval * quisk_MicPlayback.sample_rate / quisk_sound_state.sample_rate;
|
||||||
mic_count = 0;
|
mic_count = 0;
|
||||||
if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0) {
|
if (QUISK_CWKEY_DOWN || quiskSpotLevel >= 0 || multus_cw_samples) {
|
||||||
while (cwCount >= 1.0) {
|
while (cwCount >= 1.0) {
|
||||||
if (cwEnvelope < 1.0) {
|
if (cwEnvelope < 1.0) {
|
||||||
cwEnvelope += 1. / (quisk_MicPlayback.sample_rate * 5e-3); // 5 milliseconds
|
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_I = 0;
|
||||||
RawSamplePlayback.channel_Q = 1;
|
RawSamplePlayback.channel_Q = 1;
|
||||||
|
|
||||||
|
file_play_level = QuiskGetConfigDouble("file_play_level", 1.0);
|
||||||
|
|
||||||
set_num_channels (&Capture);
|
set_num_channels (&Capture);
|
||||||
set_num_channels (&quisk_Playback);
|
set_num_channels (&quisk_Playback);
|
||||||
set_num_channels (&MicCapture);
|
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)
|
PyObject * quisk_set_sparams(PyObject * self, PyObject * args, PyObject * keywds)
|
||||||
{ /* Call with keyword arguments ONLY; change local parameters */
|
{ /* Call with keyword arguments ONLY; change local parameters */
|
||||||
static char * kwlist[] = {"dc_remove_bw", "digital_output_level", "remote_control_slave", "remote_control_head",
|
static char * kwlist[] = {"dc_remove_bw", "digital_output_level", "remote_control_slave", "remote_control_head",
|
||||||
NULL} ;
|
"file_play_level", "multus_cw_samples", NULL} ;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idiidi", kwlist, &dc_remove_bw, &digital_output_level,
|
||||||
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))
|
||||||
&remote_control_slave, &remote_control_head))
|
|
||||||
return NULL;
|
return NULL;
|
||||||
Py_INCREF (Py_None);
|
Py_INCREF (Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
|
|||||||
Reference in New Issue
Block a user