Quisk version 4.2.35
This commit is contained in:
@@ -1,3 +1,15 @@
|
||||
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.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.34
|
||||
#Quisk version 4.2.35
|
||||
from .quisk import main
|
||||
|
||||
+368
-170
@@ -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
|
||||
@@ -835,12 +831,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
|
||||
@@ -848,121 +842,231 @@ 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)
|
||||
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
|
||||
@@ -973,81 +1077,172 @@ 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):
|
||||
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()
|
||||
|
||||
@@ -2028,6 +2223,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)
|
||||
@@ -3437,13 +3635,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)
|
||||
|
||||
@@ -836,13 +836,14 @@ by hand if you are a Python expert. Otherwise you can just read the values.
|
||||
<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>
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -4260,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
|
||||
|
||||
@@ -5047,6 +5060,79 @@ static PyObject * get_bandscope(PyObject * self, PyObject * args) // Called by t
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -5120,6 +5206,8 @@ static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the G
|
||||
for (i = 0; i < fft_size; i++) // multiply by window
|
||||
ptFft->samples[i] *= fft_window[i];
|
||||
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
|
||||
@@ -5170,6 +5258,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
|
||||
@@ -5995,6 +6086,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."},
|
||||
|
||||
@@ -3628,7 +3628,7 @@ class App(wx.App):
|
||||
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?
|
||||
@@ -3784,6 +3784,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
|
||||
@@ -4450,6 +4451,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)
|
||||
@@ -5883,10 +5903,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')
|
||||
@@ -6598,9 +6616,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():
|
||||
|
||||
@@ -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:
|
||||
#
|
||||
|
||||
@@ -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.34'
|
||||
Version = '4.2.35'
|
||||
|
||||
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"):
|
||||
|
||||
@@ -86,6 +86,7 @@ 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 ty_sample_start pt_sample_start;
|
||||
@@ -175,9 +176,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 +732,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 +841,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 +946,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 +1003,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
|
||||
@@ -1401,6 +1435,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 +1601,10 @@ 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} ;
|
||||
"file_play_level", NULL} ;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idii", kwlist, &dc_remove_bw, &digital_output_level,
|
||||
&remote_control_slave, &remote_control_head))
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idiid", kwlist, &dc_remove_bw, &digital_output_level,
|
||||
&remote_control_slave, &remote_control_head, &file_play_level))
|
||||
return NULL;
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
|
||||
Reference in New Issue
Block a user