3 Commits
Author SHA1 Message Date
James C. Ahlstrom 76208643a0 Quisk version 4.2.35 2024-06-28 13:54:55 -04:00
James C. Ahlstrom 31ecbce146 Quisk version 4.2.34 2024-05-23 21:17:45 -04:00
James C. Ahlstrom a1343c1264 Quisk version 4.2.33 2024-05-10 13:16:44 -04:00
9 changed files with 684 additions and 218 deletions
+27
View File
@@ -1,3 +1,30 @@
Quisk Version 4.2.35 June 2024
===============================
There is a new item "File play level" on the Config/radio/Sound screen to reduce the volume when playing files.
Use it to reduce the volume of the CQ message relative to radio sound.
Dan, AI2M, provided a patch to compile in MacOS on Apple Silicon. Thanks Dan!
I am working on better amplitude and phase adjustments when using SoftRock radios. The new corrections
screen has a spreadsheet to display the VFO and tune offset. And there is a feature to automatically
measure the corrections when using a signal generator as a source. If you use Quisk with SoftRock or similar
radios, please let me know what you think.
Quisk Version 4.2.34 May 2024
==============================
For the Hermes-Lite2 the inhibit Tx function (CN8) now releases the Spot button.
Quisk displays the band plan below the X-axis as colored bars, and there are eighteen possible colors.
If you hover the mouse over a bar, Quisk now displays the name of the mode. Recall that there is a dialog
box so you can set the band plan to what you want.
Quisk Version 4.2.33 May 2024
==============================
Added the get_level Hamlib command "l STRENGTH". It returns the S-meter reading. I finally found some Hamlib
documentation, often not an easy task. The return value of STRENGTH is in dB with zero dB equal to S9.
There are 6 dB per S-unit. So a STRENGTH of 4 is 4dB over S9, and -6, -12, -18 is S8, S7, S6. I also added
the corresponding FlexRadio command ZZSM.
Quisk Version 4.2.32 April 2024 Quisk Version 4.2.32 April 2024
================================ ================================
I found and fixed a bug in the microphone filters that can result in a buffer overrun. You should update Quisk. I found and fixed a bug in the microphone filters that can result in a buffer overrun. You should update Quisk.
+1 -1
View File
@@ -1,2 +1,2 @@
#Quisk version 4.2.32 #Quisk version 4.2.35
from .quisk import main from .quisk import main
+379 -193
View File
@@ -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
@@ -829,12 +831,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 +842,231 @@ 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)
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,81 +1077,172 @@ 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] if self.dirty:
if self.rx_tx not in data: dlg = wx.MessageDialog(self,
data[self.rx_tx] = [] "Your changes are not saved. Do you want to save them?", "Changes Were Made",
data = data[self.rx_tx] wx.OK|wx.CANCEL|wx.CANCEL_DEFAULT|wx.ICON_WARNING)
for vfo, items in data: # All items must be lists, not tuples dlg.SetOKLabel("Discard Changes")
if vfo == application.VFO: ret = dlg.ShowModal()
for i in range(len(items)): dlg.Destroy()
item = items[i] if ret == wx.ID_CANCEL:
if item[0] == application.rxFreq: return
item[1] = self.new_amplitude QS.softrock_corrections(0)
item[2] = self.new_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()
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()
@@ -2022,6 +2223,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 +3635,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 +4188,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 +4395,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):
+4 -3
View File
@@ -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>
+106
View File
@@ -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
@@ -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,79 @@ static PyObject * get_bandscope(PyObject * self, PyObject * args) // Called by t
return Py_None; 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 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;
@@ -5106,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 for (i = 0; i < fft_size; i++) // multiply by window
ptFft->samples[i] *= fft_window[i]; ptFft->samples[i] *= fft_window[i];
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 +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); //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 +6086,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."},
+89 -9
View File
@@ -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!!
@@ -658,6 +658,16 @@ class HamlibHandlerSerial:
self.app.pttButton.SetValue(0, True) self.app.pttButton.SetValue(0, True)
else: else:
self.Error(cmd, data) self.Error(cmd, data)
def ZZSM(self, cmd, data, length): # return the S-meter value in dB * 2; 0 to 260
if length == 0: # 0 to 260 is -140 to -10 dB; S9 == -73 dB == 134; dB = ZZSM / 2 - 140
i = round((self.app.hamlib_strength + 67) * 2)
if i < 0:
i = 0
elif i > 260:
i = 260
self.Write('%s%03d;' % (cmd, i))
else:
self.Error(cmd, data)
def ZZSP(self, cmd, data, length): # the split status def ZZSP(self, cmd, data, length): # the split status
if length == 0: if length == 0:
if self.app.split_rxtx: if self.app.split_rxtx:
@@ -1151,7 +1161,7 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
def GetLevel(self): def GetLevel(self):
name = self.GetParamName() name = self.GetParamName()
if name == '?': # send back supported parameters if name == '?': # send back supported parameters
self.Reply2('AGC AF', 0) self.Reply2('AGC AF STRENGTH', 0)
elif name == 'AGC': elif name == 'AGC':
if self.app.BtnAGC.GetValue(): if self.app.BtnAGC.GetValue():
self.Reply2(4, 0) self.Reply2(4, 0)
@@ -1160,6 +1170,9 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
elif name == 'AF': elif name == 'AF':
x = self.app.audio_volume # audio_volume is 0 to 1.000 x = self.app.audio_volume # audio_volume is 0 to 1.000
self.Reply2("%.5f" % x, 0) self.Reply2("%.5f" % x, 0)
elif name == 'STRENGTH':
i = round(self.app.hamlib_strength)
self.Reply2(i, 0)
else: else:
self.ErrParam() self.ErrParam()
def SetLevel(self): def SetLevel(self):
@@ -1879,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)
@@ -2230,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
@@ -3565,12 +3622,13 @@ class App(wx.App):
self.smeter_avg_seconds = 1.0 # seconds for S-meter average self.smeter_avg_seconds = 1.0 # seconds for S-meter average
self.smeter_sunits = -87.0 self.smeter_sunits = -87.0
self.smeter_usage = "smeter" # specify use of s-meter display self.smeter_usage = "smeter" # specify use of s-meter display
self.hamlib_strength = 0.0;
self.timer = time.time() # A seconds clock self.timer = time.time() # A seconds clock
self.heart_time0 = self.timer # timer to call HeartBeat at intervals self.heart_time0 = self.timer # timer to call HeartBeat at intervals
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?
@@ -3726,6 +3784,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
@@ -4392,6 +4451,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)
@@ -5043,6 +5121,7 @@ class App(wx.App):
self.smeter_sunits_time0 = self.timer self.smeter_sunits_time0 = self.timer
s = self.smeter_sunits / 6.0 # change to S units; 6db per S unit s = self.smeter_sunits / 6.0 # change to S units; 6db per S unit
s += Hardware.correct_smeter # S-meter correction for the gain, band, etc. s += Hardware.correct_smeter # S-meter correction for the gain, band, etc.
self.hamlib_strength = (s - 9.0) * 6.0 # convert to dB; 0 dB is S9
if s < 0: if s < 0:
s = 0 s = 0
if s >= 9.5: if s >= 9.5:
@@ -5824,10 +5903,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')
@@ -6519,6 +6596,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='')
@@ -6537,9 +6616,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():
+29 -1
View File
@@ -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:
# #
@@ -2103,6 +2111,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]
]
+4 -2
View File
@@ -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.32' Version = '4.2.35'
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"):
+46 -10
View File
@@ -86,6 +86,7 @@ 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 ty_sample_start pt_sample_start; 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++) { 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 +732,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 +841,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 +946,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 +1003,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
@@ -1401,6 +1435,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 +1601,10 @@ 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", NULL} ;
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idii", kwlist, &dc_remove_bw, &digital_output_level, if (!PyArg_ParseTupleAndKeywords (args, keywds, "|idiid", kwlist, &dc_remove_bw, &digital_output_level,
&remote_control_slave, &remote_control_head)) &remote_control_slave, &remote_control_head, &file_play_level))
return NULL; return NULL;
Py_INCREF (Py_None); Py_INCREF (Py_None);
return Py_None; return Py_None;