Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76208643a0 | ||
|
|
31ecbce146 | ||
|
|
a1343c1264 | ||
|
|
71146c9eaf | ||
|
|
814e4ce08c | ||
|
|
36d717636c | ||
|
|
26773d5d09 | ||
|
|
15375aa4ea | ||
|
|
7f516653d5 |
@@ -1,3 +1,63 @@
|
|||||||
|
Quisk Version 4.2.35 June 2024
|
||||||
|
===============================
|
||||||
|
There is a new item "File play level" on the Config/radio/Sound screen to reduce the volume when playing files.
|
||||||
|
Use it to reduce the volume of the CQ message relative to radio sound.
|
||||||
|
|
||||||
|
Dan, AI2M, provided a patch to compile in MacOS on Apple Silicon. Thanks Dan!
|
||||||
|
|
||||||
|
I am working on better amplitude and phase adjustments when using SoftRock radios. The new corrections
|
||||||
|
screen has a spreadsheet to display the VFO and tune offset. And there is a feature to automatically
|
||||||
|
measure the corrections when using a signal generator as a source. If you use Quisk with SoftRock or similar
|
||||||
|
radios, please let me know what you think.
|
||||||
|
|
||||||
|
Quisk Version 4.2.34 May 2024
|
||||||
|
==============================
|
||||||
|
For the Hermes-Lite2 the inhibit Tx function (CN8) now releases the Spot button.
|
||||||
|
|
||||||
|
Quisk displays the band plan below the X-axis as colored bars, and there are eighteen possible colors.
|
||||||
|
If you hover the mouse over a bar, Quisk now displays the name of the mode. Recall that there is a dialog
|
||||||
|
box so you can set the band plan to what you want.
|
||||||
|
|
||||||
|
Quisk Version 4.2.33 May 2024
|
||||||
|
==============================
|
||||||
|
Added the get_level Hamlib command "l STRENGTH". It returns the S-meter reading. I finally found some Hamlib
|
||||||
|
documentation, often not an easy task. The return value of STRENGTH is in dB with zero dB equal to S9.
|
||||||
|
There are 6 dB per S-unit. So a STRENGTH of 4 is 4dB over S9, and -6, -12, -18 is S8, S7, S6. I also added
|
||||||
|
the corresponding FlexRadio command ZZSM.
|
||||||
|
|
||||||
|
Quisk Version 4.2.32 April 2024
|
||||||
|
================================
|
||||||
|
I found and fixed a bug in the microphone filters that can result in a buffer overrun. You should update Quisk.
|
||||||
|
|
||||||
|
Some Linux digital programs can not use the usual Quisk Pulse loopback device and must use an
|
||||||
|
Alsa loopback. I added more documentation for Alsa loopbacks, added more information on the Sound
|
||||||
|
screen and I changed the default Alsa sample size to 16 bit.
|
||||||
|
|
||||||
|
Quisk now responds to the Hermes Lite2 Tx inhibit input by displaying a message and extinguishing the "Tx" light.
|
||||||
|
There is a new item "Hermes Lite2 protocol" on the Hardware screen to control this behavior.
|
||||||
|
|
||||||
|
Quisk Version 4.2.31 March 2024
|
||||||
|
================================
|
||||||
|
The "Tune" item was missing from the Midi menus. I fixed it but I didn't test it because I did
|
||||||
|
not borrow Ben's Midi controller. Please test it yourself and let me know if it works. The Midi
|
||||||
|
speed can be set from the menu too.
|
||||||
|
|
||||||
|
Quisk Version 4.2.30 March 2024
|
||||||
|
================================
|
||||||
|
A new option "Tx-Rx silence msec" on the Timing configuration screen controls how long
|
||||||
|
to suppress Rx audio when switching from Tx to Rx. The previous fixed time was 50 msec.
|
||||||
|
|
||||||
|
The SSB squelch control now works on CW too. Please test.
|
||||||
|
|
||||||
|
Quisk Version 4.2.29 January 2024
|
||||||
|
==================================
|
||||||
|
This version updates the FreeDV library files and program logic. I added a "Monitor"
|
||||||
|
item to the FreeDV menu. It routes the sound to the speakers instead of to the demodulator.
|
||||||
|
Turn on both the monitor and FDX to listen to your transmitted FreeDV signals. Please test.
|
||||||
|
|
||||||
|
Modes 700x and 1600 are finished. The other modes will be added shortly. If you use any of them
|
||||||
|
post here and I will move them to the top of the list.
|
||||||
|
|
||||||
Quisk Version 4.2.28 December 2023
|
Quisk Version 4.2.28 December 2023
|
||||||
===================================
|
===================================
|
||||||
This version includes a patch from Jon, AB8WU, to build Quisk on FreeBSD. Thanks Jon!
|
This version includes a patch from Jon, AB8WU, to build Quisk on FreeBSD. Thanks Jon!
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Quisk Software Defined Radio by N2ADR
|
# Quisk Software Defined Radio by N2ADR
|
||||||
**December 26, 2023**
|
**December 27, 2023**
|
||||||
|
|
||||||
This is Quisk, a Software Defined Radio (SDR).
|
This is Quisk, a Software Defined Radio (SDR).
|
||||||
You supply radio hardware such as Hermes Lite2 or SoftRock to convert
|
You supply radio hardware such as Hermes Lite2 or SoftRock to convert
|
||||||
@@ -14,8 +14,8 @@ The web page for this project is [https://groups.io/g/n2adr-sdr](https://groups
|
|||||||
|
|
||||||
The change log is [here](CHANGELOG.txt).
|
The change log is [here](CHANGELOG.txt).
|
||||||
|
|
||||||
The documentation is [here](docs.html).
|
Installation instructions are [here](http://james.ahlstrom.name/quisk/docs.html#Installation).
|
||||||
|
|
||||||
The help file is [here](help.html).
|
The documentation is [here](http://james.ahlstrom.name/quisk/docs.html).
|
||||||
|
|
||||||
This site is under construction. Check back soon.
|
The help file is [here](http://james.ahlstrom.name/quisk/help.html).
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
#Quisk version 4.2.29
|
#Quisk version 4.2.35
|
||||||
from .quisk import main
|
from .quisk import main
|
||||||
|
|||||||
+390
-198
@@ -1,13 +1,9 @@
|
|||||||
|
import sys, wx, wx.lib, os, re, pickle, traceback, json, copy
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
|
|
||||||
import sys, wx, wx.lib, os, re, pickle, traceback, json
|
|
||||||
# Quisk will alter quisk_conf_defaults to include the user's config file.
|
# Quisk will alter quisk_conf_defaults to include the user's config file.
|
||||||
import quisk_conf_defaults as conf
|
import quisk_conf_defaults as conf
|
||||||
import _quisk as QS
|
import _quisk as QS
|
||||||
from quisk_widgets import QuiskPushbutton, QuiskCheckbutton, QuiskBitField, SliderBoxH, SliderBoxHH
|
from quisk_widgets import QuiskPushbutton, QuiskCheckbutton, QuiskBitField, SliderBoxH, SliderBoxHH
|
||||||
|
from quisk_widgets import FreqFormatter
|
||||||
from quisk_widgets import wxVersion
|
from quisk_widgets import wxVersion
|
||||||
if wxVersion in ('2', '3'):
|
if wxVersion in ('2', '3'):
|
||||||
import wx.combo as wxcombo
|
import wx.combo as wxcombo
|
||||||
@@ -170,12 +166,16 @@ class Configuration:
|
|||||||
radio_dict["Hermes_BandDictTx"] = {}
|
radio_dict["Hermes_BandDictTx"] = {}
|
||||||
def UpdateGlobals(self):
|
def UpdateGlobals(self):
|
||||||
self.RadioName = Settings[1]
|
self.RadioName = Settings[1]
|
||||||
|
application.BandPlanC2T = {}
|
||||||
if self.RadioName == "ConfigFileRadio":
|
if self.RadioName == "ConfigFileRadio":
|
||||||
application.BandPlan = conf.BandPlan
|
application.BandPlan = conf.BandPlan
|
||||||
|
for mode, color in conf.BandPlanColors:
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
elif "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
elif "BandPlanColors" in Settings[4] and "BandPlan" in Settings[4]:
|
||||||
mode_dict = {"End":None}
|
mode_dict = {"End":None}
|
||||||
for mode, color in Settings[4]["BandPlanColors"]:
|
for mode, color in Settings[4]["BandPlanColors"]:
|
||||||
mode_dict[mode] = color
|
mode_dict[mode] = color
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
plan = []
|
plan = []
|
||||||
for freq, mode in Settings[4]["BandPlan"]:
|
for freq, mode in Settings[4]["BandPlan"]:
|
||||||
freq = int(float(freq) * 1E6 + 0.1)
|
freq = int(float(freq) * 1E6 + 0.1)
|
||||||
@@ -188,6 +188,8 @@ class Configuration:
|
|||||||
application.BandPlan = plan
|
application.BandPlan = plan
|
||||||
else:
|
else:
|
||||||
application.BandPlan = conf.BandPlan
|
application.BandPlan = conf.BandPlan
|
||||||
|
for mode, color in conf.BandPlanColors:
|
||||||
|
application.BandPlanC2T[color] = mode
|
||||||
if "MidiNoteDict" not in Settings[4]:
|
if "MidiNoteDict" not in Settings[4]:
|
||||||
Settings[4]["MidiNoteDict"] = {}
|
Settings[4]["MidiNoteDict"] = {}
|
||||||
self.settings_changed = True
|
self.settings_changed = True
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -1484,7 +1685,7 @@ class MidiKnob(QuiskControl):
|
|||||||
self.menu = wx.Menu()
|
self.menu = wx.Menu()
|
||||||
for name in application.midiControls:
|
for name in application.midiControls:
|
||||||
ctrl, func = application.midiControls[name]
|
ctrl, func = application.midiControls[name]
|
||||||
if not ctrl or not func:
|
if name != "Tune" and (not ctrl or not func):
|
||||||
continue
|
continue
|
||||||
item = self.menu.Append(-1, name)
|
item = self.menu.Append(-1, name)
|
||||||
self.Bind(wx.EVT_MENU, self.OnMenu, item)
|
self.Bind(wx.EVT_MENU, self.OnMenu, item)
|
||||||
@@ -1519,7 +1720,7 @@ class MidiJogWheel(QuiskControl):
|
|||||||
controls = wx.Menu()
|
controls = wx.Menu()
|
||||||
for name in application.midiControls:
|
for name in application.midiControls:
|
||||||
ctrl, func = application.midiControls[name]
|
ctrl, func = application.midiControls[name]
|
||||||
if not ctrl or not func:
|
if name != "Tune" and (not ctrl or not func):
|
||||||
continue
|
continue
|
||||||
item = controls.Append(-1, name)
|
item = controls.Append(-1, name)
|
||||||
self.Bind(wx.EVT_MENU, self.OnMenu, item)
|
self.Bind(wx.EVT_MENU, self.OnMenu, item)
|
||||||
@@ -2005,7 +2206,7 @@ class ControlMixin:
|
|||||||
if self.radio_name == Settings[1]: # changed for current radio
|
if self.radio_name == Settings[1]: # changed for current radio
|
||||||
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
|
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
|
||||||
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
|
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
|
||||||
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle'):
|
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable'):
|
||||||
setattr(conf, name, x)
|
setattr(conf, name, x)
|
||||||
application.ImmediateChange(name)
|
application.ImmediateChange(name)
|
||||||
elif name[0:4] in ('lin_', 'win_'):
|
elif name[0:4] in ('lin_', 'win_'):
|
||||||
@@ -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)
|
||||||
@@ -3399,14 +3603,20 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
|
|||||||
('', '', '', '', 'digital_rx8_name'),
|
('', '', '', '', 'digital_rx8_name'),
|
||||||
('', '', '', '', 'digital_rx9_name'),
|
('', '', '', '', 'digital_rx9_name'),
|
||||||
)
|
)
|
||||||
|
hRx0 = "This is almost the same as the radio sound, but it is sent to a digital mode program on a different sound device."\
|
||||||
|
" It is analog audio and it is only sent if one of the DGT- modes is selected."\
|
||||||
|
" The volume is set by the digital output level instead of the volume control so you can mute the speaker if desired."\
|
||||||
|
" It can receive a greater bandwidth than the 3000 Hz limit of SSB."
|
||||||
|
hTx0 = "This is used instead of the microphone when one of the DGT- modes is selected."\
|
||||||
|
" The normal speech clipping and filtering is not used for DGT- modes."
|
||||||
label_help = ( # Same order as sound_names
|
label_help = ( # Same order as sound_names
|
||||||
(1, "Radio Sound Output", "This is the radio sound going to the headphones or speakers."),
|
(1, "Radio Sound Output", "This is the radio sound going to the headphones or speakers."),
|
||||||
(0, "Microphone Input", "This is the monophonic microphone source. Set the channel if the source is stereo."),
|
(0, "Microphone Input", "This is the monophonic microphone source. Set the channel if the source is stereo."),
|
||||||
(0, "I/Q Rx Sample Input", "This is the sample source if it comes from a sound device, such as a SoftRock."),
|
(0, "I/Q Rx Sample Input", "This is the sample source if it comes from a sound device, such as a SoftRock."),
|
||||||
(1, "I/Q Tx Sample Output", "This is the transmit sample audio sent to a SoftRock."),
|
(1, "I/Q Tx Sample Output", "This is the transmit sample audio sent to a SoftRock."),
|
||||||
(1, "Raw Digital Output", "This sends the received I/Q data to another program as stereo."),
|
(1, "Raw Digital Output", "This sends the received I/Q data to another program as stereo."),
|
||||||
(0, "Digital Tx0 Input", "This is the transmit audio coming from a digital mode program."),
|
(0, "Digital Tx0 Input", hTx0),
|
||||||
(1, "Digital Rx0 Output", "This is the main receiver Rx0 audio going to a digital mode program."),
|
(1, "Digital Rx0 Output", hRx0),
|
||||||
(1, "Digital Rx1 Output", "This is the sub-receiver 1 audio going to a digital mode program."),
|
(1, "Digital Rx1 Output", "This is the sub-receiver 1 audio going to a digital mode program."),
|
||||||
(1, "Digital Rx2 Output", "This is the sub-receiver 2 audio going to a digital mode program."),
|
(1, "Digital Rx2 Output", "This is the sub-receiver 2 audio going to a digital mode program."),
|
||||||
(1, "Digital Rx3 Output", "This is the sub-receiver 3 audio going to a digital mode program."),
|
(1, "Digital Rx3 Output", "This is the sub-receiver 3 audio going to a digital mode program."),
|
||||||
@@ -3425,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)
|
||||||
@@ -3978,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}
|
||||||
@@ -4206,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):
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ button on the lower right.
|
|||||||
<h2>News</h2>
|
<h2>News</h2>
|
||||||
<p>
|
<p>
|
||||||
The newest versions of Linux and Raspberry Pi OS do not allow installing Quisk in the system Python package directory.
|
The newest versions of Linux and Raspberry Pi OS do not allow installing Quisk in the system Python package directory.
|
||||||
I am working on a solution for this.
|
Use the Linux Source Installation below instead.
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<p>
|
<p>
|
||||||
@@ -189,23 +189,22 @@ Many others contributed to Quisk, and are mentioned in comments in the source co
|
|||||||
<h2 id="Installation">Installation</h2>
|
<h2 id="Installation">Installation</h2>
|
||||||
<p>
|
<p>
|
||||||
Quisk is free open source software written in Python and C. It is hosted on the PyPi repository
|
Quisk is free open source software written in Python and C. It is hosted on the PyPi repository
|
||||||
<a href="https://pypi.org">https://pypi.org</a>.
|
<a href="https://pypi.org">https://pypi.org</a> and it is available on github.
|
||||||
It can be installed using the standard Python setup tools. See specific directions below.
|
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<p>
|
<p>
|
||||||
If you have Python 3 installed on your computer, just continue to use that version.
|
If you have Python 3 installed on your computer, just continue to use that version.
|
||||||
<b>Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
||||||
If you want to install a more recent version, uninstall the old version first.
|
If you want to install a more recent version of Python, uninstall the old version first.
|
||||||
Running multiple versions of Python is possible but can be confusing and is not necessary.
|
Running multiple versions of Python is possible but can be confusing and is not necessary.
|
||||||
A Quisk installation is needed for each version of Python you have.
|
A Quisk installation is needed for each version of Python you have.
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<h3 id="g0.2.2">Windows Initial Installation</h3>
|
<h3 id="g0.2.2">Windows Initial Installation</h3>
|
||||||
<p>
|
<p>
|
||||||
Windows does not include Python, so you must first install the most recent version of 64-bit Python 3.
|
Windows does not include Python, so you must first install a recent version of 64-bit Python 3.
|
||||||
If you already have 64-bit Python 3 installed, just keep using it.
|
If you already have 64-bit Python 3 installed, just keep using it.
|
||||||
<b>Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
|
||||||
There may be newer versions of Python, but until support is added, use the versions listed above.
|
There may be newer versions of Python, but until support is added, use the versions listed above.
|
||||||
Python is available from <a href="http://www.python.org/">http://www.python.org</a>.
|
Python is available from <a href="http://www.python.org/">http://www.python.org</a>.
|
||||||
There is an option to <b>Add Python to PATH</b>. You MUST select
|
There is an option to <b>Add Python to PATH</b>. You MUST select
|
||||||
@@ -280,186 +279,111 @@ python -m pip uninstall quisk
|
|||||||
</p>
|
</p>
|
||||||
<h3 id="g0.2.1">Linux Initial Installation</h3>
|
<h3 id="g0.2.1">Linux Initial Installation</h3>
|
||||||
<p>
|
<p>
|
||||||
<br>
|
|
||||||
The libsoundio-dev module is NO LONGER required!
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
Linux runs on a wide variety of computers with different processors and versions of Linux.
|
Linux runs on a wide variety of computers with different processors and versions of Linux.
|
||||||
Therefore Quisk is compiled for each one. To do this, Python and a number of other packages are required.
|
Therefore Quisk is compiled for each one. To do this, Python and a number of other packages are required.
|
||||||
Most likely both Python2 and Python3 are already installed on your computer. Either the 32-bit or 64-bit
|
Most likely both Python2 and Python3 are already installed on your computer.
|
||||||
version will work. On my Ubuntu machine, "python2" starts Python2 and "python3" starts Python3.
|
On my Ubuntu machine, "python2" starts Python2 and "python3" starts Python3.
|
||||||
Just "python" starts Python2, but this will change as Python2 is phased out.
|
Just "python" starts Python2, but this will change as Python2 is phased out.
|
||||||
Python2 is obsolete, so you should only install Quisk to Python3, and use "python3" to start it.
|
<b>Python2 is obsolete</b>, so you should only install Quisk to Python3, and use "python3" to start it.
|
||||||
Install these packages. Some of these can be installed from Python using "pip",
|
Install these packages by using your package manager, not Python pip.
|
||||||
but it is better to use your package manager because pip and the package manager do not coordinate.
|
|
||||||
If newer versions of these packages become available, Linux will notify you.
|
If newer versions of these packages become available, Linux will notify you.
|
||||||
Use the most recent version of python3-wxgtk available.
|
Use the most recent version of python3-wxgtk available for your Python version.
|
||||||
<br><br>
|
<br><br>
|
||||||
</p>
|
</p>
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>Package Description</th>
|
|
||||||
<th>Python3 Package</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Discrete fourier transform</td>
|
|
||||||
<td>sudo apt-get install libfftw3-dev</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Alsa sound library</td>
|
|
||||||
<td>sudo apt-get install libasound2-dev</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Portaudio sound library</td>
|
|
||||||
<td>sudo apt-get install portaudio19-dev</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Pulseaudio sound library</td>
|
|
||||||
<td>sudo apt-get install libpulse-dev</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Python development</td>
|
|
||||||
<td>sudo apt-get install python3-dev</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Python library development</td>
|
|
||||||
<td>sudo apt-get install libpython3-dev</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>wxPython GUI library</td>
|
|
||||||
<td>sudo apt-get install python3-wxgtk4.0</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Python USB</td>
|
|
||||||
<td>sudo apt-get install python3-usb</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Python serial port</td>
|
|
||||||
<td>sudo apt-get install python3-serial</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Python installation tools</td>
|
|
||||||
<td>sudo apt-get install python3-setuptools</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Pip package installer</td>
|
|
||||||
<td>sudo apt-get install python3-pip</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<p>
|
<p>
|
||||||
<br><br>
|
These are part of your operating system. You only install them once unless it changes.
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<pre>
|
||||||
|
sudo apt-get install libfftw3-dev
|
||||||
|
sudo apt-get install libasound2-dev
|
||||||
|
sudo apt-get install portaudio19-dev
|
||||||
|
sudo apt-get install libpulse-dev
|
||||||
|
sudo apt-get install python3-dev
|
||||||
|
sudo apt-get install libpython3-dev
|
||||||
|
</pre>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
These are part of Python. You only install them once unless the Python started by "python3" changes.
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<pre>
|
||||||
|
sudo apt-get install python3-wxgtk4.0
|
||||||
|
sudo apt-get install python3-usb
|
||||||
|
sudo apt-get install python3-serial
|
||||||
|
sudo apt-get install python3-setuptools
|
||||||
|
</pre>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
If you want to use the SoapySDR module make sure that SoapySDR is installed from its Pothosware site before you build Quisk.
|
If you want to use the SoapySDR module make sure that SoapySDR is installed from its Pothosware site before you build Quisk.
|
||||||
Otherwise Quisk will not have the files it needs. You can install SoapySDR later, but then you
|
Otherwise Quisk will not have the files it needs. You can install SoapySDR later, but then you
|
||||||
will need to reinstall Quisk. This is also true of any other external packages that have their own C source.
|
will need to run "make" again. This is also true of any other external packages that have their own C source.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
</p>
|
</p>
|
||||||
<h3>Linux Pip Installation</h3>
|
<h3>Linux Pip Installation</h3>
|
||||||
|
<br>
|
||||||
<p>
|
<p>
|
||||||
You can use the Python program "pip" to install Quisk into the Python package directory. This is the easiest
|
<b> Installation of Quisk using Python tools like pip is deprecated</b>.
|
||||||
method, but it will not install the source files. If you want the source files see below.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
sudo -H python3 -m pip install --upgrade quisk
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
This will install the Python files and all other files except for the C source. This is the easiest method
|
|
||||||
if you do not want to work in C.
|
|
||||||
To run Quisk, use this command from a terminal:
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
python3 -m quisk
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
You can create a panel launcher on your desktop with the command to run Quisk.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
If the installation of Quisk fails, you can force re-installation:
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
</p>
|
</p>
|
||||||
<pre>
|
<br>
|
||||||
sudo -H python3 -m pip uninstall quisk
|
|
||||||
sudo -H python3 -m pip install --no-cache-dir --force-reinstall quisk
|
|
||||||
</pre>
|
|
||||||
<p>
|
<p>
|
||||||
|
Installation to the system Python is no longer allowed, and you may get the message "error: externally-managed-environment".
|
||||||
|
If you used pip to install Quisk you can continue to upgrade with "sudo python3 -m pip install --upgrade quisk"
|
||||||
|
until you get this error message. But it is best to uninstall the pip Quisk. If you installed with pipx, uninstall that too.
|
||||||
|
</p>
|
||||||
<br>
|
<br>
|
||||||
You can also install an older version of Quisk. You may need to do that if the most recent version fails for some reason. Use:
|
<pre>
|
||||||
|
sudo python3 -m pip uninstall quisk
|
||||||
|
pipx uninstall quisk
|
||||||
|
</pre>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
Then use the Linux Source Installation.
|
||||||
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
sudo -H python3 -m pip install quisk==4.1.51
|
<h3 id="LSI">Linux Source Installation</h3>
|
||||||
|
<p>
|
||||||
|
On Linux, Quisk is installed from github, and it is contained in a "quisk" directory. First you need to decide where to put the
|
||||||
|
quisk directory. Anywhere will do, but it is best to use a directory where you have write permission so
|
||||||
|
you don't need to use "sudo". The examples below assume you used your home directory indicated by "~". Just replace "~"
|
||||||
|
by your desired location.
|
||||||
|
</p>
|
||||||
<br>
|
<br>
|
||||||
|
<p>
|
||||||
|
To install a fresh copy of Quisk, change to your desired directory, clone the github site, change to the quisk directory and run "make".
|
||||||
|
Then run the python program quisk.py.
|
||||||
|
</p>
|
||||||
<br>
|
<br>
|
||||||
|
<pre>
|
||||||
|
cd ~
|
||||||
|
git clone https://github.com/jimahlstrom/quisk
|
||||||
|
cd quisk
|
||||||
|
make
|
||||||
|
python3 quisk.py
|
||||||
|
</pre>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
If "make" fails, you probably have missing packages or missing "-dev" packages. Try to figure out what is missing from the error messages.
|
||||||
|
You only clone github for the initial installation. To update Quisk, use pull:
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<pre>
|
||||||
|
cd ~/quisk
|
||||||
|
git pull
|
||||||
|
</pre>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
You run Quisk by running the file quisk.py with python3. For example, "python3 /home/jim/quisk/quisk.py".
|
||||||
|
You can create a desktop icon to do that, but the method depends on your version of Linux.
|
||||||
|
<b>Never run Quisk with root privelege by using sudo. Fix the permission problem instead.</b>
|
||||||
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
|
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
|
||||||
Then set your sound devices for that radio; the device for the radio speakers, the microphone and so forth. All configuration
|
Then set your sound devices for that radio; the device for the radio speakers, the microphone and so forth. All configuration
|
||||||
is (mostly) from the Config button. Ignore old directions and don't bother with a config file.
|
is (mostly) from the Config button. Ignore old directions and don't bother with a config file.
|
||||||
<br>
|
To uninstall Quisk, just delete the quisk directory.
|
||||||
<br>
|
|
||||||
To edit the Quisk Python files you need to know where they are. To find out, import quisk and print it:
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
</p>
|
|
||||||
<pre>
|
|
||||||
jim@IntelNUC:~$ python3
|
|
||||||
Python 3.6.9 (default, Oct 8 2020, 12:12:24)
|
|
||||||
[GCC 8.4.0] on linux
|
|
||||||
Type "help", "copyright", "credits" or "license" for more information.
|
|
||||||
--- import quisk
|
|
||||||
--- quisk
|
|
||||||
module 'quisk' from '/usr/local/lib/python3.6/dist-packages/quisk/__init__.py'
|
|
||||||
---
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
<br>
|
|
||||||
<h3>Linux Pip Upgrade</h3>
|
|
||||||
<p>
|
|
||||||
To upgrade to a newer version of Quisk, use pip:
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
sudo python3 -m pip install --upgrade quisk
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
</p>
|
|
||||||
<h3>Linux Uninstall Quisk</h3>
|
|
||||||
<p>
|
|
||||||
sudo python3 -m pip uninstall quisk
|
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
|
||||||
<h3>Linux Source Installation</h3>
|
|
||||||
<p>
|
|
||||||
The previous method using pip is simple, but will not install the C source files. If you want those proceed as follows.
|
|
||||||
Go to <a href="https://pypi.org/project/quisk/">https://pypi.org/project/quisk</a> and under "Navigation"
|
|
||||||
select "Download files". Download the quisk-x-x-xx.tar.gz file. This is the source file. The ".whl" files are for Windows.
|
|
||||||
Change directory to where the file was downloaded, probably "Downloads", and uncompress and untar it.
|
|
||||||
<br><br>
|
|
||||||
cd ~/Downloads
|
|
||||||
<br>
|
|
||||||
tar zxf quisk-x.x.xx.tar.gz
|
|
||||||
<br><br>
|
|
||||||
Change directories to the Quisk directory:
|
|
||||||
<br><br>
|
|
||||||
cd quisk-x.x.xx
|
|
||||||
<br><br>
|
|
||||||
Now compile Quisk with the "make" command:
|
|
||||||
<br><br>
|
|
||||||
make quisk3
|
|
||||||
<br><br>
|
|
||||||
If "make" fails, you probably have missing packages or missing "-dev" packages. Try to figure out what is missing from the error messages.
|
|
||||||
Now you can run quisk with the command:
|
|
||||||
<br><br>
|
|
||||||
python3 quisk.py
|
|
||||||
<br><br>
|
|
||||||
At this point you have a choice of where to install Quisk. You could just rename the quisk-x.x.xx directory
|
|
||||||
to a more convenient name, like "quisk" in your home directory, and run Quisk from there.
|
|
||||||
This is convenient if you want to
|
|
||||||
alter the Quisk code. Or you could install Quisk into the Python system.
|
|
||||||
<br><br>
|
|
||||||
python2 setup.py build
|
|
||||||
<br>
|
|
||||||
sudo python2 setup.py install
|
|
||||||
<br><br>
|
|
||||||
</p>
|
|
||||||
<h3 id="g0.2.4">Quisk Files</h3>
|
<h3 id="g0.2.4">Quisk Files</h3>
|
||||||
<p>
|
<p>
|
||||||
<br>
|
<br>
|
||||||
@@ -912,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>
|
||||||
@@ -1546,22 +1471,29 @@ remember the settings.
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
If you don't have PulseAudio or don't want to use it, you can use the ALSA loopback device.
|
If your digital program only uses Alsa devices or you don't want to use PulseAudio,
|
||||||
The ALSA loopback device works the same way as the Windows VAC. First
|
you can use the ALSA loopback device.
|
||||||
create the loopback device with the command "modprobe snd-aloop" (you
|
It works the same way as the Windows VAC. First create the loopback device with this command:
|
||||||
will need to be root). You can create the loopback device
|
<pre>
|
||||||
when the system starts, but the way to do that depends on your version of
|
sudo modprobe snd-aloop index=19
|
||||||
Linux. I added snd-aloop to /etc/modules. You could put the modprobe command in /etc/rc.local instead.
|
</pre>
|
||||||
|
The "index" is the card number, and it is best to use a number higher than your regular cards.
|
||||||
|
The loopback device will be gone on the next restart.
|
||||||
|
You can create the loopback device every time the system starts, but the way to do that depends on your version of Linux.
|
||||||
|
You could add this line to /etc/modprobe.d/alsa-base.conf:
|
||||||
|
<pre>
|
||||||
|
options snd-aloop index=19
|
||||||
|
</pre>
|
||||||
|
Or you could add snd-aloop to /etc/modules or put the modprobe command in /etc/rc.local instead.
|
||||||
Restart Linux. Now you can enter "cat /proc/asound/cards" to
|
Restart Linux. Now you can enter "cat /proc/asound/cards" to
|
||||||
print out your sound cards, and you should see a "Loopback" card
|
print out your sound cards, and you should see a "Loopback" card listed.
|
||||||
listed. The cards are also shown on the Quisk Sound
|
The Loopback card has one side that connects to Quisk and
|
||||||
screen. The Loopback card has one side that connects to Quisk and
|
|
||||||
another side that connects to your digital program. For the Quisk
|
another side that connects to your digital program. For the Quisk
|
||||||
side connect both Digital Input and Digital Output to Loopback,0.
|
side connect both Digital Input and Digital Output to the Loopback at hw:19,0.
|
||||||
Note that the Loopback card is full duplex, and handles both
|
Note that the Loopback card is full duplex, and handles both
|
||||||
input and output. There are actually eight loopbacks created at once,
|
input and output. There are actually eight loopbacks created at once,
|
||||||
but we are only using subdevice 0. For the digital program side, set the input and
|
but we are only using subdevice 0. For the digital program side, set the input and
|
||||||
output to "Loopback,1".
|
output to the Loopback at hw:19,1.
|
||||||
Your audio is now connected and you
|
Your audio is now connected and you
|
||||||
should be able to receive digital signals. Be sure to test your
|
should be able to receive digital signals. Be sure to test your
|
||||||
transmit signal off the air. You may need to reduce power to
|
transmit signal off the air. You may need to reduce power to
|
||||||
|
|||||||
@@ -586,7 +586,7 @@ double quiskAudio24p6Coefs[36] = { 0.001199140008010727, 0.005953815908571521, 0
|
|||||||
-0.008973143363910490, 0.011839435384729619, 0.003122601697996235, -0.008665733839154772, -0.000319602525571569, 0.008621055763448699,
|
-0.008973143363910490, 0.011839435384729619, 0.003122601697996235, -0.008665733839154772, -0.000319602525571569, 0.008621055763448699,
|
||||||
0.005953815908571521, 0.001199140008010727} ;
|
0.005953815908571521, 0.001199140008010727} ;
|
||||||
|
|
||||||
// Sample 48 kHz, pass 6, stop 8, ripple 0.5 dB, atten 80 dB. Stop 0.16667.
|
// Sample 48 kHz, pass 6, stop 8, ripple 0.5 dB, atten 80 dB. Pass 0.125. Stop 0.16667.
|
||||||
double quiskAudio48p6Coefs[71] = { 0.000324677779792651, 0.001038192405843851, 0.002082337464742640, 0.002938055116822665,
|
double quiskAudio48p6Coefs[71] = { 0.000324677779792651, 0.001038192405843851, 0.002082337464742640, 0.002938055116822665,
|
||||||
0.002811674671240099, 0.001132278163758895, -0.001835394694865431, -0.004766409947196539, -0.005833007003128954, -0.003914015660218843,
|
0.002811674671240099, 0.001132278163758895, -0.001835394694865431, -0.004766409947196539, -0.005833007003128954, -0.003914015660218843,
|
||||||
0.000296099005521212, 0.004239593489554206, 0.004918390640978499, 0.001100872619673516, -0.005241634309386784,
|
0.000296099005521212, 0.004239593489554206, 0.004918390640978499, 0.001100872619673516, -0.005241634309386784,
|
||||||
|
|||||||
@@ -32,8 +32,17 @@ typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t siz
|
|||||||
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size);
|
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size);
|
||||||
|
|
||||||
/* advanced freedv open options required by some modes */
|
/* advanced freedv open options required by some modes */
|
||||||
struct freedv_advanced { // from freedv_api.h
|
struct freedv_advanced {
|
||||||
int interleave_frames;
|
int interleave_frames; // now unused but remains to prevent breaking API for
|
||||||
|
// legacy apps
|
||||||
|
|
||||||
|
// parameters for FREEDV_MODE_FSK_LDPC
|
||||||
|
int M; // 2 or 4 FSK
|
||||||
|
int Rs; // Symbol rate Hz
|
||||||
|
int Fs; // Sample rate Hz
|
||||||
|
int first_tone; // Freq of first tone Hz
|
||||||
|
int tone_spacing; // Spacing between tones Hz
|
||||||
|
char *codename; // LDPC codename, from codes listed in ldpc_codes.c
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
@@ -66,11 +75,14 @@ static int handle_index = -1;
|
|||||||
// need access to these here and in quisk.c
|
// need access to these here and in quisk.c
|
||||||
int n_speech_sample_rate = 8000;
|
int n_speech_sample_rate = 8000;
|
||||||
int n_modem_sample_rate = 8000;
|
int n_modem_sample_rate = 8000;
|
||||||
|
int n_max_modem_samples = 1000;
|
||||||
|
|
||||||
#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000
|
#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000
|
||||||
|
#define MAX_MODEM_SAMPLES 8192 // maximum of n_max_modem_samples for any mode
|
||||||
|
#define MAX_SPEECH_SAMPLES 8192 // maximum of n_speech_samples for any mode
|
||||||
static struct _rx_channel{
|
static struct _rx_channel{
|
||||||
struct freedv * hFreedv;
|
struct freedv * hFreedv;
|
||||||
COMP * demod_in;
|
short * demod_in;
|
||||||
int rxdata_index;
|
int rxdata_index;
|
||||||
short speech_out[SPEECH_BUF_SIZE]; // output buffer
|
short speech_out[SPEECH_BUF_SIZE]; // output buffer
|
||||||
int speech_available; // number of samples in output buffer
|
int speech_available; // number of samples in output buffer
|
||||||
@@ -163,15 +175,11 @@ static void GetAddrs(void)
|
|||||||
if ( ! hLib) {
|
if ( ! hLib) {
|
||||||
handle_index = 2;
|
handle_index = 2;
|
||||||
GET_HANDLE2;
|
GET_HANDLE2;
|
||||||
if ( ! hLib) {
|
|
||||||
handle_index = 3;
|
|
||||||
GET_HANDLE3;
|
|
||||||
if ( ! hLib) {
|
if ( ! hLib) {
|
||||||
handle_index = 4;
|
handle_index = 4;
|
||||||
GET_HANDLE4;
|
GET_HANDLE4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (hLib) {
|
if (hLib) {
|
||||||
freedv_get_version = GET_ADDR("freedv_get_version");
|
freedv_get_version = GET_ADDR("freedv_get_version");
|
||||||
if (freedv_get_version != NULL) {
|
if (freedv_get_version != NULL) {
|
||||||
@@ -231,19 +239,17 @@ static void GetAddrs(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int count, int bank) // Called from the sound thread.
|
static int quisk_freedv_rx(short * sSamples, double * dsamples, int count, int bank) // Called from the sound thread.
|
||||||
{ // Input digital modulation is cSamples; decoded voice is dsamples. Each "bank" is a stream of audio.
|
{ // Input digital modulation is sSamples; decoded voice is dsamples. Each "bank" is a stream of audio.
|
||||||
// caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction
|
// caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction
|
||||||
// output data will be returned at n_speech_sample_rate with corresponding block sizes
|
// output data will be returned at n_speech_sample_rate with corresponding block sizes
|
||||||
int i, nout, need, have, sync;
|
int i, nout, need, have, sync;
|
||||||
int n_speech_samples;
|
int n_speech_samples;
|
||||||
complex double cx;
|
|
||||||
double scale = (double)CLIP32 / CLIP16; // convert 32 bits to 16 bits
|
|
||||||
struct freedv * hF;
|
struct freedv * hF;
|
||||||
struct _rx_channel * pCh;
|
struct _rx_channel * pCh;
|
||||||
int nCountOut = 1; // In case we have a higher (or lower) value for o/p count
|
int nCountOut = 1; // In case we have a higher (or lower) value for o/p count
|
||||||
|
|
||||||
if (cSamples == NULL) { // shutdown
|
if (sSamples == NULL) { // shutdown
|
||||||
for (i = 0; i < MAX_RECEIVERS; i++) {
|
for (i = 0; i < MAX_RECEIVERS; i++) {
|
||||||
if (rx_channel[i].demod_in) {
|
if (rx_channel[i].demod_in) {
|
||||||
free(rx_channel[i].demod_in);
|
free(rx_channel[i].demod_in);
|
||||||
@@ -277,20 +283,11 @@ static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int cou
|
|||||||
nout = 0;
|
nout = 0;
|
||||||
need = freedv_nin(hF);
|
need = freedv_nin(hF);
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
cx = cRxFilterOut(cSamples[i], bank, 0);
|
pCh->demod_in[pCh->rxdata_index] = sSamples[i];
|
||||||
if (rxMode == FDV_L) // lower sideband
|
|
||||||
cx = conj(cx);
|
|
||||||
#if 1 // Try it the other way and remove scaling factor
|
|
||||||
pCh->demod_in[pCh->rxdata_index].real = creal(cx);
|
|
||||||
pCh->demod_in[pCh->rxdata_index].imag = cimag(cx);
|
|
||||||
#else
|
|
||||||
pCh->demod_in[pCh->rxdata_index].real = (creal(cx) - cimag(cx));
|
|
||||||
pCh->demod_in[pCh->rxdata_index].imag = 0;
|
|
||||||
#endif
|
|
||||||
pCh->rxdata_index++;
|
pCh->rxdata_index++;
|
||||||
if (pCh->rxdata_index >= need) {
|
if (pCh->rxdata_index >= need) {
|
||||||
if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space
|
if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space
|
||||||
have = freedv_comprx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
|
have = freedv_rx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
|
||||||
if (freedv_version > 10)
|
if (freedv_version > 10)
|
||||||
sync = freedv_get_sync(hF);
|
sync = freedv_get_sync(hF);
|
||||||
else
|
else
|
||||||
@@ -325,7 +322,7 @@ static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int cou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++)
|
for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++)
|
||||||
dsamples[nout] = pCh->speech_out[nout] * scale * 0.7;
|
dsamples[nout] = pCh->speech_out[nout] * ((double)CLIP32 / CLIP16 * 0.99);
|
||||||
if (nout) {
|
if (nout) {
|
||||||
pCh->speech_available -= nout;
|
pCh->speech_available -= nout;
|
||||||
memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
|
memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
|
||||||
@@ -339,7 +336,7 @@ static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int cou
|
|||||||
return nout;
|
return nout;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count) // Called from the sound thread.
|
static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count, int is_real) // Called from the sound thread.
|
||||||
{ // Input voice samples are dsamples; output digital modulation is cSamples.
|
{ // Input voice samples are dsamples; output digital modulation is cSamples.
|
||||||
int i, nout;
|
int i, nout;
|
||||||
int n_speech_samples;
|
int n_speech_samples;
|
||||||
@@ -351,16 +348,7 @@ static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int cou
|
|||||||
static short *real_mod_out = NULL;
|
static short *real_mod_out = NULL;
|
||||||
|
|
||||||
if (dsamples == NULL) { // shutdown
|
if (dsamples == NULL) { // shutdown
|
||||||
if (mod_out)
|
return 0; // nothing to do
|
||||||
free(mod_out);
|
|
||||||
mod_out = NULL;
|
|
||||||
if (real_mod_out)
|
|
||||||
free(real_mod_out);
|
|
||||||
real_mod_out = NULL;
|
|
||||||
if (speech_in)
|
|
||||||
free(speech_in);
|
|
||||||
speech_in = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if ( ! rx_channel[0].hFreedv)
|
if ( ! rx_channel[0].hFreedv)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -371,13 +359,13 @@ static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int cou
|
|||||||
if( nRatio < 1 || nRatio > 6 )
|
if( nRatio < 1 || nRatio > 6 )
|
||||||
nRatio = 1;
|
nRatio = 1;
|
||||||
if (mod_out == NULL) { // initialize
|
if (mod_out == NULL) { // initialize
|
||||||
mod_out = (COMP *)malloc(sizeof(COMP) * n_nom_modem_samples);
|
mod_out = (COMP *)malloc(sizeof(COMP) * MAX_MODEM_SAMPLES);
|
||||||
memset(mod_out, 0, sizeof(COMP) * n_nom_modem_samples);
|
memset(mod_out, 0, sizeof(COMP) * MAX_MODEM_SAMPLES);
|
||||||
speech_in = (short*)malloc(sizeof(short) * n_speech_samples);
|
speech_in = (short*)malloc(sizeof(short) * MAX_SPEECH_SAMPLES);
|
||||||
speech_index=0;
|
speech_index=0;
|
||||||
mod_index=0;
|
mod_index=0;
|
||||||
real_mod_out = (short *)malloc(sizeof(short) * n_nom_modem_samples);
|
real_mod_out = (short *)malloc(sizeof(short) * MAX_MODEM_SAMPLES);
|
||||||
memset(real_mod_out, 0, sizeof(short) * n_nom_modem_samples);
|
memset(real_mod_out, 0, sizeof(short) * MAX_MODEM_SAMPLES);
|
||||||
//if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples );
|
//if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples );
|
||||||
}
|
}
|
||||||
nout = 0;
|
nout = 0;
|
||||||
@@ -386,7 +374,7 @@ static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int cou
|
|||||||
if (speech_index >= n_speech_samples) {
|
if (speech_index >= n_speech_samples) {
|
||||||
// Calculate a new block, but first write out the rest of the old block
|
// Calculate a new block, but first write out the rest of the old block
|
||||||
// 800xa uses real not complex
|
// 800xa uses real not complex
|
||||||
if( freedv_current_mode == FREEDV_MODE_800XA ) {
|
if( is_real ) {
|
||||||
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
||||||
cSamples[nout++] = real_mod_out[mod_index];
|
cSamples[nout++] = real_mod_out[mod_index];
|
||||||
freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in);
|
freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in);
|
||||||
@@ -403,7 +391,7 @@ static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int cou
|
|||||||
else { // write out samples
|
else { // write out samples
|
||||||
for( int j=0; j<nRatio; j++) {
|
for( int j=0; j<nRatio; j++) {
|
||||||
if (mod_index < n_nom_modem_samples ) {
|
if (mod_index < n_nom_modem_samples ) {
|
||||||
if( freedv_current_mode == FREEDV_MODE_800XA ) {
|
if( is_real ) {
|
||||||
cSamples[nout++] = real_mod_out[mod_index];
|
cSamples[nout++] = real_mod_out[mod_index];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -471,21 +459,15 @@ static void CloseFreedv(void) // Called from the GUI thread or sound thread
|
|||||||
freedv_close(rx_channel[i].hFreedv);
|
freedv_close(rx_channel[i].hFreedv);
|
||||||
rx_channel[i].hFreedv = NULL;
|
rx_channel[i].hFreedv = NULL;
|
||||||
}
|
}
|
||||||
#if 0 // g8kbb - unnecessary - quisk_freedv_rx() will do it
|
|
||||||
if (rx_channel[i].demod_in) {
|
|
||||||
free(rx_channel[i].demod_in);
|
|
||||||
rx_channel[i].demod_in = NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
quisk_freedv_rx(NULL, NULL, 0, 0);
|
quisk_freedv_rx(NULL, NULL, 0, 0);
|
||||||
quisk_freedv_tx(NULL, NULL, 0);
|
quisk_freedv_tx(NULL, NULL, 0, 0);
|
||||||
freedv_current_mode = -1;
|
freedv_current_mode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
||||||
{
|
{
|
||||||
int i, n_max_modem_samples;
|
int i;
|
||||||
struct freedv * hF;
|
struct freedv * hF;
|
||||||
|
|
||||||
if ( ! hLib)
|
if ( ! hLib)
|
||||||
@@ -500,7 +482,7 @@ static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
|||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
case 4:
|
case 4:
|
||||||
printf("freedv_open: codec2 library freedvpkg/libcodec2_32|64.dll|so found, version %d\n", freedv_version);
|
printf("freedv_open: codec2 library freedvpkg/libcodec2_64 found, version %d\n", freedv_version);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
printf("freedv_open: Could not find the FreeDV codec2 shared library\n");
|
printf("freedv_open: Could not find the FreeDV codec2 shared library\n");
|
||||||
@@ -530,18 +512,10 @@ static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
|||||||
return 0; // failure
|
return 0; // failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (requested_mode == FREEDV_MODE_700E) {
|
|
||||||
if (handle_index >= 3) { // Quisk provided codec2
|
|
||||||
CloseFreedv();
|
|
||||||
requested_mode = -1;
|
|
||||||
if (DEBUG)
|
|
||||||
printf("freedv_open: Failure because mode 700E requires a system installation of codec2\n");
|
|
||||||
return 0; // failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) {
|
if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) {
|
||||||
struct freedv_advanced adv;
|
struct freedv_advanced adv;
|
||||||
|
memset(&adv, 0, sizeof(adv));
|
||||||
adv.interleave_frames = interleave_frames;
|
adv.interleave_frames = interleave_frames;
|
||||||
hF = freedv_open_advanced(requested_mode, &adv);
|
hF = freedv_open_advanced(requested_mode, &adv);
|
||||||
}
|
}
|
||||||
@@ -579,7 +553,7 @@ static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
|||||||
rx_channel[i].playing = 0;
|
rx_channel[i].playing = 0;
|
||||||
if (rx_channel[i].demod_in)
|
if (rx_channel[i].demod_in)
|
||||||
free(rx_channel[i].demod_in);
|
free(rx_channel[i].demod_in);
|
||||||
rx_channel[i].demod_in = (COMP *)malloc(sizeof(COMP) * n_max_modem_samples);
|
rx_channel[i].demod_in = (short *)malloc(sizeof(short) * n_max_modem_samples);
|
||||||
if( rx_channel[i].demod_in == NULL) {
|
if( rx_channel[i].demod_in == NULL) {
|
||||||
CloseFreedv();
|
CloseFreedv();
|
||||||
requested_mode = -1;
|
requested_mode = -1;
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
extern int freedv_current_mode; // the current FreeDV running mode
|
extern int freedv_current_mode; // the current FreeDV running mode
|
||||||
|
|
||||||
|
// available speech modes
|
||||||
#define FREEDV_MODE_1600 0
|
#define FREEDV_MODE_1600 0
|
||||||
#define FREEDV_MODE_700 1
|
|
||||||
#define FREEDV_MODE_700B 2
|
|
||||||
#define FREEDV_MODE_2400A 3
|
#define FREEDV_MODE_2400A 3
|
||||||
#define FREEDV_MODE_2400B 4
|
#define FREEDV_MODE_2400B 4
|
||||||
#define FREEDV_MODE_800XA 5
|
#define FREEDV_MODE_800XA 5
|
||||||
#define FREEDV_MODE_700C 6
|
#define FREEDV_MODE_700C 6
|
||||||
#define FREEDV_MODE_700D 7
|
#define FREEDV_MODE_700D 7
|
||||||
#define FREEDV_MODE_2020 8
|
|
||||||
#define FREEDV_MODE_700E 13
|
#define FREEDV_MODE_700E 13
|
||||||
|
#define FREEDV_MODE_2020 8
|
||||||
|
#define FREEDV_MODE_2020B 16
|
||||||
|
|
||||||
|
// available data modes
|
||||||
|
#define FREEDV_MODE_FSK_LDPC 9
|
||||||
|
#define FREEDV_MODE_DATAC1 10
|
||||||
|
#define FREEDV_MODE_DATAC3 12
|
||||||
|
#define FREEDV_MODE_DATAC0 14
|
||||||
|
#define FREEDV_MODE_DATAC4 18
|
||||||
|
#define FREEDV_MODE_DATAC13 19
|
||||||
|
|
||||||
|
// Sample rates used
|
||||||
|
#define FREEDV_FS_8000 8000
|
||||||
|
#define FREEDV_FS_16000 16000
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -168,6 +168,7 @@ static void modem_status(void)
|
|||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
|
||||||
static int fdComm = -1; // File descriptor to read the serial port
|
static int fdComm = -1; // File descriptor to read the serial port
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+70
-52
@@ -372,7 +372,7 @@ static void process_alc(complex double * cSamples, int count, struct alc * pt, r
|
|||||||
|
|
||||||
static int tx_filter(complex double * filtered, int count)
|
static int tx_filter(complex double * filtered, int count)
|
||||||
{ // Input samples are creal(filtered), output is filtered. The input rate must be 8000 or 48000 sps.
|
{ // Input samples are creal(filtered), output is filtered. The input rate must be 8000 or 48000 sps.
|
||||||
int i, is_ssb;
|
int i, is_ssb, mic_interp;
|
||||||
int sample_rate = 8000;
|
int sample_rate = 8000;
|
||||||
double dsample, dtmp, magn;
|
double dsample, dtmp, magn;
|
||||||
complex double csample;
|
complex double csample;
|
||||||
@@ -423,9 +423,10 @@ static int tx_filter(complex double * filtered, int count)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
mic_interp = MIC_OUT_RATE / sample_rate;
|
||||||
// check size of dsamples[] and csamples[] buffer
|
// check size of dsamples[] and csamples[] buffer
|
||||||
if (count > samples_size) {
|
if (count > samples_size) {
|
||||||
samples_size = count * 2;
|
samples_size = count * 2 * mic_interp;
|
||||||
if (dsamples)
|
if (dsamples)
|
||||||
free(dsamples);
|
free(dsamples);
|
||||||
if (csamples)
|
if (csamples)
|
||||||
@@ -541,8 +542,8 @@ static int tx_filter(complex double * filtered, int count)
|
|||||||
// remove clipping distortion
|
// remove clipping distortion
|
||||||
count = quisk_cDecimate(csamples, count, &cfiltAudio3, 1);
|
count = quisk_cDecimate(csamples, count, &cfiltAudio3, 1);
|
||||||
// Interpolate up to 48000
|
// Interpolate up to 48000
|
||||||
if (MIC_OUT_RATE != sample_rate)
|
if (mic_interp != 1)
|
||||||
count = quisk_cInterpolate(csamples, count, &cfiltInterp, MIC_OUT_RATE / sample_rate);
|
count = quisk_cInterpolate(csamples, count, &cfiltInterp, mic_interp);
|
||||||
// convert back to 16 bits
|
// convert back to 16 bits
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
filtered[i] = csamples[i] * CLIP16;
|
filtered[i] = csamples[i] * CLIP16;
|
||||||
@@ -573,8 +574,8 @@ static int tx_filter(complex double * filtered, int count)
|
|||||||
// remove clipping distortion
|
// remove clipping distortion
|
||||||
count = quisk_dFilter(dsamples, count, &dfiltAudio3);
|
count = quisk_dFilter(dsamples, count, &dfiltAudio3);
|
||||||
// Interpolate up to 48000
|
// Interpolate up to 48000
|
||||||
if (MIC_OUT_RATE != sample_rate)
|
if (mic_interp != 1)
|
||||||
count = quisk_dInterpolate(dsamples, count, &dfiltInterp, MIC_OUT_RATE / sample_rate);
|
count = quisk_dInterpolate(dsamples, count, &dfiltInterp, mic_interp);
|
||||||
// convert back to 16 bits
|
// convert back to 16 bits
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
filtered[i] = dsamples[i] * CLIP16;
|
filtered[i] = dsamples[i] * CLIP16;
|
||||||
@@ -626,32 +627,32 @@ static int tx_filter_digital(complex double * filtered, int count)
|
|||||||
static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
||||||
{ // Input samples are creal(filtered), output is filtered.
|
{ // Input samples are creal(filtered), output is filtered.
|
||||||
// This filter is used for digital voice.
|
// This filter is used for digital voice.
|
||||||
// changes by Dave Roberts, G8KBB, for additional modes June, 2020.
|
// Changes by Dave Roberts, G8KBB, for additional modes June, 2020.
|
||||||
|
// Modified by N2ADR January, 2024.
|
||||||
int i;
|
int i;
|
||||||
// The input sample rate is mic_sample_rate, either 8000 or 48000 sps.
|
// The input sample rate is mic_sample_rate, either 8000 or 48000 sps.
|
||||||
// The FreeDV codec requires an input n_speech_sample_rate of either 8000 or 16000 sps depending on the mode. Input is always real.
|
// The FreeDV codec requires an input speech_sample_rate of either 8000 or 16000 sps depending on the mode. Input is always real.
|
||||||
// The FreeDV codec will output samples at n_modem_sample_rate of 8000, 16000 or 48000 sps depending on the mode, and output may be real or complex.
|
// The FreeDV codec will output samples at modem_sample_rate of 8000 or 48000 sps depending on the mode, and output may be real or complex.
|
||||||
double dtmp, magn, dsample;
|
double dtmp, magn, dsample;
|
||||||
|
int modem_sample_rate, speech_sample_rate, use_filter, is_real;
|
||||||
static int samples_size = 0;
|
static int samples_size = 0;
|
||||||
static int last_rx_mode;
|
|
||||||
static int last_freedv_mode;
|
|
||||||
static int do_init = 1;
|
static int do_init = 1;
|
||||||
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
|
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
|
||||||
static double time_long, time_short;
|
static double time_long, time_short;
|
||||||
static double inMax=0.3;
|
static double inMax=0.3;
|
||||||
static double * dsamples = NULL;
|
static double * dsamples = NULL;
|
||||||
static struct quisk_cFilter filter2;
|
static struct quisk_cFilter filter1, filter2;
|
||||||
static struct quisk_dFilter filtDecim;
|
static struct quisk_dFilter filtDecim48to16, filtDecim16to8;;
|
||||||
static struct quisk_cFilter cfiltInterp;
|
static struct quisk_cFilter cfiltInterp8to48;
|
||||||
|
|
||||||
if (do_init) { // initialization
|
if (do_init) { // initialization
|
||||||
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
|
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
|
||||||
do_init = 0;
|
do_init = 0;
|
||||||
last_rx_mode = -1;
|
quisk_filt_cInit(&filter1, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // at 48000 sps pass 3000 stop 4000 times two plus 3000
|
||||||
last_freedv_mode = -1;
|
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // at 8000 sps pass 1500 stop 1800 times two plus 1500
|
||||||
memset(&filter2, 0, sizeof(filter2));
|
quisk_filt_cInit(&cfiltInterp8to48, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
||||||
quisk_filt_dInit(&filtDecim, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // pass 3000, stop 4000
|
quisk_filt_dInit(&filtDecim48to16, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double)); // pass 6000 stop 8000
|
||||||
quisk_filt_cInit(&cfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
quisk_filt_dInit(&filtDecim16to8, quiskFilt16dec8Coefs, sizeof(quiskFilt16dec8Coefs)/sizeof(double)); // pass 3000 stop 4000
|
||||||
Ymax = pow(10.0, - 1 / 20.0); // maximum y
|
Ymax = pow(10.0, - 1 / 20.0); // maximum y
|
||||||
Xmax = pow(10.0, 3 / 20.0); // x where slope is zero; for x > Xmax, y == Ymax
|
Xmax = pow(10.0, 3 / 20.0); // x where slope is zero; for x > Xmax, y == Ymax
|
||||||
Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression
|
Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression
|
||||||
@@ -660,36 +661,38 @@ static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
|||||||
bbb = -2.0 * aaa * Xmax;
|
bbb = -2.0 * aaa * Xmax;
|
||||||
ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax;
|
ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax;
|
||||||
}
|
}
|
||||||
if (last_freedv_mode != freedv_current_mode) { // correct filter2 depends on freedv mode
|
switch (freedv_current_mode) { // n_modem_sample_rate and n_speech_sample_rate may not be valid
|
||||||
last_freedv_mode = freedv_current_mode;
|
case FREEDV_MODE_2400A:
|
||||||
last_rx_mode = -1; // re-tune new filter
|
case FREEDV_MODE_2400B:
|
||||||
if (filter2.cSamples)
|
use_filter = 1; // center frequency 3000
|
||||||
free(filter2.cSamples);
|
is_real = 1;
|
||||||
filter2.cSamples = NULL;
|
modem_sample_rate = 48000;
|
||||||
if (filter2.cpxCoefs)
|
speech_sample_rate = 8000;
|
||||||
free(filter2.cpxCoefs);
|
break;
|
||||||
filter2.cpxCoefs = NULL;
|
case FREEDV_MODE_2020:
|
||||||
if (filter2.cBuf)
|
use_filter = 0;
|
||||||
free(filter2.cBuf);
|
is_real = 0;
|
||||||
filter2.cBuf = NULL;
|
modem_sample_rate = 8000;
|
||||||
switch(freedv_current_mode) {
|
speech_sample_rate = 16000;
|
||||||
case FREEDV_MODE_700D: // filter2 is not used
|
break;
|
||||||
|
case FREEDV_MODE_800XA:
|
||||||
|
use_filter = 2; // center frequency 1500
|
||||||
|
is_real = 1;
|
||||||
|
modem_sample_rate = 8000;
|
||||||
|
speech_sample_rate = 8000;
|
||||||
break;
|
break;
|
||||||
// TODO: add modes that return real samples that must be converted to complex
|
|
||||||
default:
|
default:
|
||||||
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // pass 1500, stop 1800
|
use_filter = 0;
|
||||||
|
is_real = 0;
|
||||||
|
modem_sample_rate = 8000;
|
||||||
|
speech_sample_rate = 8000;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
if ( ! filtered) { // rxMode changed
|
||||||
if (last_rx_mode != (int)rxMode) { // change to sideband
|
quisk_filt_tune((struct quisk_dFilter *)&filter1, 3000.0 / 48000.0, rxMode != FDV_L);
|
||||||
last_rx_mode = rxMode;
|
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1500.0 / 8000.0, rxMode != FDV_L);
|
||||||
if (rxMode == FDV_U) // upper sideband
|
|
||||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / n_modem_sample_rate, 1);
|
|
||||||
else if (rxMode == FDV_L) // lower sideband
|
|
||||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / n_modem_sample_rate, 0);
|
|
||||||
}
|
|
||||||
if ( ! filtered)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
// check size of dsamples[] buffer
|
// check size of dsamples[] buffer
|
||||||
if (count > samples_size) {
|
if (count > samples_size) {
|
||||||
samples_size = count * 2;
|
samples_size = count * 2;
|
||||||
@@ -701,12 +704,25 @@ static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
|||||||
for (i = 0; i < count; i++)
|
for (i = 0; i < count; i++)
|
||||||
dsamples[i] = creal(filtered[i]) / CLIP16; // normalize to 1.0000
|
dsamples[i] = creal(filtered[i]) / CLIP16; // normalize to 1.0000
|
||||||
// Decimate to 8000 Hz or to 16000 depending on mode (and hence setting of sample rate)
|
// Decimate to 8000 Hz or to 16000 depending on mode (and hence setting of sample rate)
|
||||||
if (quisk_sound_state.mic_sample_rate == 48000)
|
if (quisk_sound_state.mic_sample_rate == 48000) {
|
||||||
count = quisk_dDecimate(dsamples, count, &filtDecim, quisk_sound_state.mic_sample_rate / n_speech_sample_rate);
|
switch (speech_sample_rate) {
|
||||||
else if (quisk_sound_state.mic_sample_rate == 8000 && n_speech_sample_rate != 8000)
|
case 16000:
|
||||||
|
count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3);
|
||||||
|
break;
|
||||||
|
case 8000:
|
||||||
|
count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3);
|
||||||
|
count = quisk_dDecimate(dsamples, count, &filtDecim16to8, 2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
QuiskPrintf("Failure to convert speech input rate in tx_filter_freedv\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (quisk_sound_state.mic_sample_rate == 8000 && speech_sample_rate != 8000) {
|
||||||
QuiskPrintf("Failure to convert input rate in tx_filter_freedv\n");
|
QuiskPrintf("Failure to convert input rate in tx_filter_freedv\n");
|
||||||
|
}
|
||||||
// Measure average peak input audio level and limit
|
// Measure average peak input audio level and limit
|
||||||
dtmp = 1.0 / n_speech_sample_rate; // sample time
|
dtmp = 1.0 / speech_sample_rate; // sample time
|
||||||
time_long = 1.0 - exp(- dtmp / 3.000);
|
time_long = 1.0 - exp(- dtmp / 3.000);
|
||||||
time_short = 1.0 - exp(- dtmp / 0.005);
|
time_short = 1.0 - exp(- dtmp / 0.005);
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
@@ -730,13 +746,15 @@ static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
|||||||
}
|
}
|
||||||
//QuiskWavWriteD(&hWav, dsamples, count);
|
//QuiskWavWriteD(&hWav, dsamples, count);
|
||||||
if (encode && pt_quisk_freedv_tx) // Encode audio into digital modulation
|
if (encode && pt_quisk_freedv_tx) // Encode audio into digital modulation
|
||||||
count = (* pt_quisk_freedv_tx)(filtered, dsamples, count);
|
count = (* pt_quisk_freedv_tx)(filtered, dsamples, count, is_real);
|
||||||
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
|
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
|
||||||
if (filter2.cSamples) // convert float samples to complex with an analytic filter
|
if (use_filter == 1) // convert float samples to complex with an analytic filter
|
||||||
|
count = quisk_cCDecimate(filtered, count, &filter1, 1);
|
||||||
|
else if (use_filter == 2)
|
||||||
count = quisk_cCDecimate(filtered, count, &filter2, 1);
|
count = quisk_cCDecimate(filtered, count, &filter2, 1);
|
||||||
// Interpolate up to 48000
|
// Interpolate up to 48000
|
||||||
if (MIC_OUT_RATE != n_modem_sample_rate )
|
if (MIC_OUT_RATE != modem_sample_rate )
|
||||||
count = quisk_cInterpolate(filtered, count, &cfiltInterp, MIC_OUT_RATE / n_modem_sample_rate);
|
count = quisk_cInterpolate(filtered, count, &cfiltInterp8to48, MIC_OUT_RATE / modem_sample_rate);
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <complex.h> // Use native C99 complex type for fftw3
|
#include <complex.h> // Use native C99 complex type for fftw3
|
||||||
#include <fftw3.h>
|
#include <fftw3.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -107,7 +108,7 @@ static PyObject * pyApp; // Application instance
|
|||||||
static int fft_size; // size of fft, e.g. 1024
|
static int fft_size; // size of fft, e.g. 1024
|
||||||
int data_width; // number of points to return as graph data; fft_size * n
|
int data_width; // number of points to return as graph data; fft_size * n
|
||||||
static int graph_width; // width of the graph in pixels
|
static int graph_width; // width of the graph in pixels
|
||||||
rx_mode_type rxMode; // 0 to 13: CWL, CWU, LSB, USB, AM, FM, EXT, DGT-U, DGT-L, DGT-IQ, IMD, FDV-U, FDV-L, DGT-FM
|
rx_mode_type rxMode; // 0 to 13: CWL, CWU, LSB, USB, AM, FM, EXT, DGT_U, DGT_L, DGT_IQ, IMD, FDV_U, FDV_L, DGT_FM
|
||||||
int quisk_noise_blanker; // noise blanker level, 0 for off
|
int quisk_noise_blanker; // noise blanker level, 0 for off
|
||||||
int quiskTxHoldState; // hold Tx until the repeater frequency shift is complete
|
int quiskTxHoldState; // hold Tx until the repeater frequency shift is complete
|
||||||
int quisk_is_vna; // zero for normal program, one for the VNA program
|
int quisk_is_vna; // zero for normal program, one for the VNA program
|
||||||
@@ -150,9 +151,15 @@ static int waterfall_scroll_mode = 1; // draw the first lines multiple times
|
|||||||
static int quisk_use_sidetone; // is there a sidetone volume control?
|
static int 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 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
|
||||||
|
static bool quisk_tx_inhibit;
|
||||||
|
|
||||||
static int multirx_data_width; // width of graph data to return
|
static int multirx_data_width; // width of graph data to return
|
||||||
static int multirx_fft_width; // size of FFT samples
|
static int multirx_fft_width; // size of FFT samples
|
||||||
@@ -179,6 +186,7 @@ int quisk_sidetoneFreq; // frequency in hertz for the sidetone
|
|||||||
int quisk_start_cw_delay = 15; // milliseconds to delay output on CW key down
|
int quisk_start_cw_delay = 15; // milliseconds to delay output on CW key down
|
||||||
int quisk_start_ssb_delay = 100; // milliseconds to discard output on SSB etc. key down
|
int quisk_start_ssb_delay = 100; // milliseconds to discard output on SSB etc. key down
|
||||||
static int maximum_tx_secs; // Failsafe timeout for Tx in seconds
|
static int maximum_tx_secs; // Failsafe timeout for Tx in seconds
|
||||||
|
static int TxRxSilenceMsec = 50; // Play silence in msec on Tx to Rx
|
||||||
static int key_is_down = 0; // internal key state up or down
|
static int key_is_down = 0; // internal key state up or down
|
||||||
|
|
||||||
static double agcReleaseGain=80; // AGC maximum gain
|
static double agcReleaseGain=80; // AGC maximum gain
|
||||||
@@ -1843,6 +1851,7 @@ static int quisk_process_decimate(complex double * cSamples, int nSamples, int b
|
|||||||
static int quisk_process_demodulate(complex double * cSamples, double * dsamples, int nSamples, int bank, int nFilter, rx_mode_type rx_mode)
|
static int quisk_process_demodulate(complex double * cSamples, double * dsamples, int nSamples, int bank, int nFilter, rx_mode_type rx_mode)
|
||||||
{ // Changes here will require changes to get_filter_rate();
|
{ // Changes here will require changes to get_filter_rate();
|
||||||
int i;
|
int i;
|
||||||
|
bool isLSB;
|
||||||
complex double cx;
|
complex double cx;
|
||||||
double d, di, dd;
|
double d, di, dd;
|
||||||
static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0};
|
static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0};
|
||||||
@@ -1918,6 +1927,10 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
|||||||
}
|
}
|
||||||
if(bank == 0)
|
if(bank == 0)
|
||||||
dAutoNotch(dsamples, nSamples, rit_freq, quisk_filter_srate);
|
dAutoNotch(dsamples, nSamples, rit_freq, quisk_filter_srate);
|
||||||
|
if (ssb_squelch_enabled) {
|
||||||
|
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||||
|
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
||||||
|
}
|
||||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio12p2, 2);
|
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio12p2, 2);
|
||||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
||||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||||
@@ -1937,6 +1950,10 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
|||||||
}
|
}
|
||||||
if(bank == 0)
|
if(bank == 0)
|
||||||
dAutoNotch(dsamples, nSamples, rit_freq, quisk_filter_srate);
|
dAutoNotch(dsamples, nSamples, rit_freq, quisk_filter_srate);
|
||||||
|
if (ssb_squelch_enabled) {
|
||||||
|
ssb_squelch(dsamples, nSamples, quisk_filter_srate, MeasureSquelch + bank);
|
||||||
|
d_delay(dsamples, nSamples, bank, SQUELCH_FFT_SIZE);
|
||||||
|
}
|
||||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio12p2, 2);
|
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio12p2, 2);
|
||||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
||||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||||
@@ -2138,41 +2155,101 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FDV_U: // digital voice
|
case FDV_U: // digital voice
|
||||||
case FDV_L: // Extra modes added by Dave Roberts, G8KBB, June 2020.
|
case FDV_L: // Extra modes added by Dave Roberts, G8KBB, June 2020. Modified by N2ADR 2024.
|
||||||
quisk_check_freedv_mode();
|
quisk_check_freedv_mode();
|
||||||
// added conditional for 2400A/B mmodes
|
// current coding assumes input rate n_modem_sample_rate is 8000 or 48000.
|
||||||
// current coding assumes modem sample rate is 8000 16000 or 48000
|
// decimate:
|
||||||
if( n_modem_sample_rate <= 16000 )
|
switch (n_modem_sample_rate) {
|
||||||
|
case 8000:
|
||||||
|
quisk_filter_srate = quisk_decim_srate / 6;
|
||||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3);
|
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3);
|
||||||
if (bank == 0)
|
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
|
||||||
process_agc(&Agc1, cSamples, nSamples, 1);
|
break;
|
||||||
|
case 48000:
|
||||||
|
quisk_filter_srate = quisk_decim_srate;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
quisk_filter_srate = quisk_decim_srate / 6;
|
||||||
|
QuiskPrintf("Bad FreeDV modem rate in quisk_process_demodulate\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// filter and demodulate to mono audio:
|
||||||
|
isLSB = (rx_mode == FDV_L);
|
||||||
|
complex double cBuf[nSamples];
|
||||||
|
for (i = 0; i < nSamples; i++) {
|
||||||
|
cx = cRxFilterOut(cSamples[i], bank, nFilter);
|
||||||
|
if (isLSB)
|
||||||
|
dd = creal(cx) + cimag(cx);
|
||||||
else
|
else
|
||||||
process_agc(&Agc2, cSamples, nSamples, 1);
|
dd = creal(cx) - cimag(cx);
|
||||||
|
if(bank == 0) {
|
||||||
|
measure_audio_sum += dd * dd;
|
||||||
|
measure_audio_count += 1;
|
||||||
|
}
|
||||||
|
cBuf[i] = dd;
|
||||||
|
}
|
||||||
// Perhaps decimate by an additional fraction
|
// Perhaps decimate by an additional fraction
|
||||||
if (quisk_decim_srate != 48000) {
|
if (quisk_decim_srate != 48000) {
|
||||||
dd = quisk_decim_srate / 48000.0;
|
dd = quisk_decim_srate / 48000.0;
|
||||||
nSamples = cFracDecim(cSamples, nSamples, dd);
|
nSamples = cFracDecim(cBuf, nSamples, dd);
|
||||||
quisk_decim_srate = 48000;
|
quisk_decim_srate = 48000;
|
||||||
}
|
}
|
||||||
quisk_filter_srate = n_speech_sample_rate;
|
if (bank == 0)
|
||||||
// added conditional for 2400A/B mmodes
|
process_agc(&Agc1, cBuf, nSamples, 0);
|
||||||
if( n_modem_sample_rate == 8000 )
|
else
|
||||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
|
process_agc(&Agc2, cBuf, nSamples, 0);
|
||||||
// pass data decimated down to n_modem_sample_rate
|
// pass data decimated down to n_modem_sample_rate
|
||||||
// return data is as n_speech_sample_rate
|
// return data is at n_speech_sample_rate
|
||||||
if (pt_quisk_freedv_rx)
|
#if 0
|
||||||
nSamples = (* pt_quisk_freedv_rx)(cSamples, dsamples, nSamples, bank);
|
static double dmax_in = 0;
|
||||||
if(bank == 0) {
|
|
||||||
for (i = 0; i < nSamples; i++) {
|
for (i = 0; i < nSamples; i++) {
|
||||||
measure_audio_sum += dsamples[i] * dsamples[i];
|
dd = fabs(creal(cBuf[i])) / CLIP16;
|
||||||
measure_audio_count += 1;
|
if (dd > dmax_in)
|
||||||
|
dmax_in = dd;
|
||||||
}
|
}
|
||||||
|
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||||
|
//printf("Max sample in %.0lf\n", dmax_in);
|
||||||
|
dmax_in = 0;
|
||||||
}
|
}
|
||||||
// current coding assumes speech sample rate is only 8000 or 16000 - needs tweak for others
|
#endif
|
||||||
|
if (nSamples > n_max_modem_samples) {
|
||||||
|
printf ("FreeDV: nSamples %d exceeds max_modem_samples %d\n", nSamples, n_max_modem_samples);
|
||||||
|
nSamples = n_max_modem_samples;
|
||||||
|
}
|
||||||
|
short sBuf[nSamples];
|
||||||
|
for (i = 0; i < nSamples; i++)
|
||||||
|
sBuf[i] = (short)(creal(cBuf[i]) / CLIP16);
|
||||||
|
if (freedv_monitor) {
|
||||||
|
for (i = 0; i < nSamples; i++)
|
||||||
|
dsamples[i] = (float)sBuf[i] * CLIP16;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (pt_quisk_freedv_rx)
|
||||||
|
nSamples = (* pt_quisk_freedv_rx)(sBuf, dsamples, nSamples, bank);
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
static double dmax_out = 0;
|
||||||
|
for (i = 0; i < nSamples; i++) {
|
||||||
|
dd = fabs(dsamples[i]) / CLIP16;
|
||||||
|
if (dd > dmax_out)
|
||||||
|
dmax_out = dd;
|
||||||
|
}
|
||||||
|
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||||
|
//printf(" Max sample out %.0lf\n", dmax_out);
|
||||||
|
dmax_out = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// current coding assumes output rate n_speech_sample_rate is 8000 or 16000
|
||||||
|
switch (n_speech_sample_rate) {
|
||||||
|
case 8000:
|
||||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||||
if( n_speech_sample_rate == 8000 )
|
|
||||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||||
break;
|
break;
|
||||||
|
case 16000:
|
||||||
|
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||||
measured_audio = sqrt(measure_audio_sum / measure_audio_count) / CLIP32 * 1e6;
|
measured_audio = sqrt(measure_audio_sum / measure_audio_count) / CLIP32 * 1e6;
|
||||||
@@ -2395,7 +2472,7 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
|
|||||||
quisk_sound_state.sample_rate;
|
quisk_sound_state.sample_rate;
|
||||||
nout = (int)dOutCounter; // number of samples to output
|
nout = (int)dOutCounter; // number of samples to output
|
||||||
dOutCounter -= nout;
|
dOutCounter -= nout;
|
||||||
playSilence = (int)(quisk_sound_state.playback_rate * 50e-3); // Play silence after sidetone ends, number of samples
|
playSilence = (int)(quisk_sound_state.playback_rate * 1E-3 * TxRxSilenceMsec); // Play silence after sidetone ends, number of samples
|
||||||
keyupEnvelope = 0;
|
keyupEnvelope = 0;
|
||||||
if (quisk_active_sidetone == 2 && QUISK_CWKEY_DOWN) { // Play sidetone instead of radio for CW
|
if (quisk_active_sidetone == 2 && QUISK_CWKEY_DOWN) { // Play sidetone instead of radio for CW
|
||||||
if (! sidetoneIsOn) { // turn on sidetone
|
if (! sidetoneIsOn) { // turn on sidetone
|
||||||
@@ -2669,7 +2746,7 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
|
|||||||
multirx_cSamples[0][i] = dsamples2[i] + I * dsamples2[i];
|
multirx_cSamples[0][i] = dsamples2[i] + I * dsamples2[i];
|
||||||
process_agc(&Agc3, multirx_cSamples[0], n, 0);
|
process_agc(&Agc3, multirx_cSamples[0], n, 0);
|
||||||
}
|
}
|
||||||
play_sound_interface(quiskPlaybackDevices[QUISK_INDEX_SUB_RX1], n, multirx_cSamples[0], 1, 1.0);
|
play_sound_interface(quiskPlaybackDevices[QUISK_INDEX_SUB_RX1], n, multirx_cSamples[0], 1, digital_output_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perhaps decimate by an additional fraction
|
// Perhaps decimate by an additional fraction
|
||||||
@@ -2877,7 +2954,7 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args)
|
|||||||
break;
|
break;
|
||||||
case FDV_U: // digital voice
|
case FDV_U: // digital voice
|
||||||
case FDV_L:
|
case FDV_L:
|
||||||
filter_srate = n_speech_sample_rate;
|
filter_srate = n_modem_sample_rate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//QuiskPrintf("Filter rate %d\n", filter_srate);
|
//QuiskPrintf("Filter rate %d\n", filter_srate);
|
||||||
@@ -3084,6 +3161,10 @@ 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)
|
||||||
|
return PyInt_FromLong((long)quisk_tx_inhibit);
|
||||||
Py_INCREF (Py_None);
|
Py_INCREF (Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
@@ -3113,13 +3194,15 @@ static PyObject * read_fftw_wisdom(PyObject * self, PyObject * args)
|
|||||||
static PyObject * set_params(PyObject * self, PyObject * args, PyObject * keywds)
|
static PyObject * set_params(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[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip",
|
static char * kwlist[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip",
|
||||||
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause", NULL} ;
|
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause",
|
||||||
|
"freedv_monitor", NULL} ;
|
||||||
int i, nbytes, read_error, clip, bscope_size, hermes_pause;
|
int i, nbytes, read_error, clip, bscope_size, hermes_pause;
|
||||||
|
|
||||||
nbytes = read_error = clip = bscope_size = hermes_pause = -1;
|
nbytes = read_error = clip = bscope_size = hermes_pause = -1;
|
||||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidi", kwlist,
|
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidii", kwlist,
|
||||||
&quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip,
|
&quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip,
|
||||||
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause))
|
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause,
|
||||||
|
&freedv_monitor))
|
||||||
return NULL;
|
return NULL;
|
||||||
if (nbytes != -1) {
|
if (nbytes != -1) {
|
||||||
py_sample_rx_bytes = nbytes;
|
py_sample_rx_bytes = nbytes;
|
||||||
@@ -3685,6 +3768,7 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
|
|||||||
//code_version = quisk_hermes_to_pc[3];
|
//code_version = quisk_hermes_to_pc[3];
|
||||||
if ((quisk_hermes_to_pc[0] & 0x01) != 0) // C1
|
if ((quisk_hermes_to_pc[0] & 0x01) != 0) // C1
|
||||||
quisk_sound_state.overrange++;
|
quisk_sound_state.overrange++;
|
||||||
|
quisk_tx_inhibit = (quisk_hermes_to_pc[0] & 0x02) == 0; // should limit to Hermes Lite2 only
|
||||||
hardware_ptt = buf[start] & 0x01; // C0 bit zero is PTT
|
hardware_ptt = buf[start] & 0x01; // C0 bit zero is PTT
|
||||||
quisk_hardware_cwkey = (buf[start] & 0x04) >> 2; // C0 bit two is CW key state
|
quisk_hardware_cwkey = (buf[start] & 0x04) >> 2; // C0 bit two is CW key state
|
||||||
switch (hl2_txbuf_state) {
|
switch (hl2_txbuf_state) {
|
||||||
@@ -3796,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
|
||||||
@@ -3901,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
|
||||||
@@ -4168,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
|
||||||
|
|
||||||
@@ -4629,6 +4734,9 @@ static PyObject * ImmediateChange(PyObject * self, PyObject * args) // called fr
|
|||||||
else if ( ! strcmp(name, "maximum_tx_secs")) {
|
else if ( ! strcmp(name, "maximum_tx_secs")) {
|
||||||
maximum_tx_secs = QuiskGetConfigInt(name, 0);
|
maximum_tx_secs = QuiskGetConfigInt(name, 0);
|
||||||
}
|
}
|
||||||
|
else if ( ! strcmp(name, "TxRxSilenceMsec")) {
|
||||||
|
TxRxSilenceMsec = QuiskGetConfigInt(name, 50);
|
||||||
|
}
|
||||||
Py_INCREF (Py_None);
|
Py_INCREF (Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
@@ -4952,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;
|
||||||
@@ -5025,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
|
||||||
@@ -5075,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
|
||||||
@@ -5786,6 +5972,7 @@ static PyObject * record_app(PyObject * self, PyObject * args)
|
|||||||
quisk_start_cw_delay = QuiskGetConfigInt("start_cw_delay", 15);
|
quisk_start_cw_delay = QuiskGetConfigInt("start_cw_delay", 15);
|
||||||
quisk_start_ssb_delay = QuiskGetConfigInt("start_ssb_delay", 100);
|
quisk_start_ssb_delay = QuiskGetConfigInt("start_ssb_delay", 100);
|
||||||
maximum_tx_secs = QuiskGetConfigInt("maximum_tx_secs", 0);
|
maximum_tx_secs = QuiskGetConfigInt("maximum_tx_secs", 0);
|
||||||
|
TxRxSilenceMsec = QuiskGetConfigInt("TxRxSilenceMsec", 50);
|
||||||
quisk_sound_state.sample_rate = rate;
|
quisk_sound_state.sample_rate = rate;
|
||||||
fft_sample_rate = rate;
|
fft_sample_rate = rate;
|
||||||
is_little_endian = 1; // Test machine byte order
|
is_little_endian = 1; // Test machine byte order
|
||||||
@@ -5899,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."},
|
||||||
|
|||||||
@@ -315,10 +315,12 @@ extern play_state_t quisk_play_state; // startup, receiving, sidetone
|
|||||||
extern int freedv_current_mode; // current FreeDV mode; 700D, @)@) etc.
|
extern int freedv_current_mode; // current FreeDV mode; 700D, @)@) etc.
|
||||||
extern int n_modem_sample_rate; // Receive data, decimate to modem_sample_rate, FreeDV codec output data at speech_sample_rate
|
extern int n_modem_sample_rate; // Receive data, decimate to modem_sample_rate, FreeDV codec output data at speech_sample_rate
|
||||||
extern int n_speech_sample_rate; // Microphone decimate to speech_sample_rate, Freedv codec output data at modem_sample_rate, interpolate to 48000
|
extern int n_speech_sample_rate; // Microphone decimate to speech_sample_rate, Freedv codec output data at modem_sample_rate, interpolate to 48000
|
||||||
|
extern int n_max_modem_samples; // maximum input to freedv_rx()
|
||||||
extern int quisk_start_cw_delay; // milliseconds to delay output on serial or MIDI CW key down
|
extern int quisk_start_cw_delay; // milliseconds to delay output on serial or MIDI CW key down
|
||||||
extern int quisk_start_ssb_delay; // milliseconds to discard output for all modes except CW
|
extern int quisk_start_ssb_delay; // milliseconds to discard output for all modes except CW
|
||||||
extern struct sound_dev * quiskPlaybackDevices[]; // array of Playback sound devices
|
extern struct sound_dev * quiskPlaybackDevices[]; // array of Playback sound devices
|
||||||
extern int quisk_close_file_play;
|
extern int quisk_close_file_play;
|
||||||
|
extern double digital_output_level;
|
||||||
|
|
||||||
extern PyObject * quisk_set_spot_level(PyObject * , PyObject *);
|
extern PyObject * quisk_set_spot_level(PyObject * , PyObject *);
|
||||||
extern PyObject * quisk_get_tx_filter(PyObject * , PyObject *);
|
extern PyObject * quisk_get_tx_filter(PyObject * , PyObject *);
|
||||||
@@ -404,8 +406,8 @@ void quisk_set_play_state(void);
|
|||||||
void quisk_poll_hardware_key(void);
|
void quisk_poll_hardware_key(void);
|
||||||
|
|
||||||
// Functions supporting digital voice codecs
|
// Functions supporting digital voice codecs
|
||||||
typedef int (* ty_dvoice_codec_rx)(complex double *, double *, int, int);
|
typedef int (* ty_dvoice_codec_rx)(short *, double *, int, int);
|
||||||
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int);
|
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int, int);
|
||||||
extern ty_dvoice_codec_rx pt_quisk_freedv_rx;
|
extern ty_dvoice_codec_rx pt_quisk_freedv_rx;
|
||||||
extern ty_dvoice_codec_tx pt_quisk_freedv_tx;
|
extern ty_dvoice_codec_tx pt_quisk_freedv_tx;
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
@@ -1762,6 +1775,12 @@ class GraphDisplay(wx.Window):
|
|||||||
dc.SetTextForeground(conf.color_graph_msg_fg)
|
dc.SetTextForeground(conf.color_graph_msg_fg)
|
||||||
dc.SetBackgroundMode(wx.SOLID)
|
dc.SetBackgroundMode(wx.SOLID)
|
||||||
dc.DrawText(self.display_text, 0, 0)
|
dc.DrawText(self.display_text, 0, 0)
|
||||||
|
if application.tx_inhibit:
|
||||||
|
dc.SetFont(self.font)
|
||||||
|
dc.SetTextBackground(conf.color_graph_msg_bg)
|
||||||
|
dc.SetTextForeground("red")
|
||||||
|
dc.SetBackgroundMode(wx.SOLID)
|
||||||
|
dc.DrawText(" *** Tx Inhibit ***", 0, self.chary * 15 // 10)
|
||||||
def DrawFilter(self, dc):
|
def DrawFilter(self, dc):
|
||||||
dc.SetPen(wx.TRANSPARENT_PEN)
|
dc.SetPen(wx.TRANSPARENT_PEN)
|
||||||
dc.SetLogicalFunction(wx.COPY)
|
dc.SetLogicalFunction(wx.COPY)
|
||||||
@@ -1873,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)
|
||||||
@@ -2224,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
|
||||||
@@ -3497,6 +3560,8 @@ class App(wx.App):
|
|||||||
self.midi_message = []
|
self.midi_message = []
|
||||||
self.idName2Button = {}
|
self.idName2Button = {}
|
||||||
self.midi_handler = None
|
self.midi_handler = None
|
||||||
|
self.tx_inhibit = 0
|
||||||
|
self.old_tx_inhibit = 0
|
||||||
if conf.use_rx_udp == 10: # Hermes UDP protocol
|
if conf.use_rx_udp == 10: # Hermes UDP protocol
|
||||||
self.bandscope_clock = conf.rx_udp_clock
|
self.bandscope_clock = conf.rx_udp_clock
|
||||||
else:
|
else:
|
||||||
@@ -3557,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?
|
||||||
@@ -3718,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
|
||||||
@@ -3762,7 +3829,8 @@ class App(wx.App):
|
|||||||
# Find the data width from a list of preferred sizes; it is the width of returned graph data.
|
# Find the data width from a list of preferred sizes; it is the width of returned graph data.
|
||||||
# The graph_width is the width of data_width that is displayed.
|
# The graph_width is the width of data_width that is displayed.
|
||||||
if conf.window_width > 0:
|
if conf.window_width > 0:
|
||||||
wFrame, h = frame.GetClientSize().Get() # client window width
|
#wFrame, h = frame.GetClientSize().Get() # client window width
|
||||||
|
wFrame = conf.window_width - 15 # Thanks to Nick Abramenko, RA3PKJ
|
||||||
graph = GraphScreen(frame, self.width//2, self.width//2, None) # make a GraphScreen to calculate borders
|
graph = GraphScreen(frame, self.width//2, self.width//2, None) # make a GraphScreen to calculate borders
|
||||||
self.graph_width = wFrame - (graph.width - graph.graph_width) # less graph borders equals actual graph_width
|
self.graph_width = wFrame - (graph.width - graph.graph_width) # less graph borders equals actual graph_width
|
||||||
graph.Destroy()
|
graph.Destroy()
|
||||||
@@ -4383,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)
|
||||||
@@ -4704,11 +4791,15 @@ class App(wx.App):
|
|||||||
self.freedv_menu = QuiskMenu("freedv_menu")
|
self.freedv_menu = QuiskMenu("freedv_menu")
|
||||||
msg = conf.freedv_tx_msg
|
msg = conf.freedv_tx_msg
|
||||||
QS.freedv_set_options(mode=conf.freedv_modes[0][1], tx_msg=msg, DEBUG=0, squelch=1)
|
QS.freedv_set_options(mode=conf.freedv_modes[0][1], tx_msg=msg, DEBUG=0, squelch=1)
|
||||||
|
self.freedv_menu.AppendCheckItem("Monitor", self.OnFreedvMonitor)
|
||||||
|
self.freedv_menu.AppendSeparator()
|
||||||
for mode, index in conf.freedv_modes:
|
for mode, index in conf.freedv_modes:
|
||||||
item = self.freedv_menu.AppendRadioItem(mode, self.OnFreedvMenu, mode == self.freedv_mode)
|
item = self.freedv_menu.AppendRadioItem(mode, self.OnFreedvMenu, mode == self.freedv_mode)
|
||||||
self.freedv_menu_items[index] = item
|
self.freedv_menu_items[index] = item
|
||||||
if mode == self.freedv_mode: # Restore mode
|
if mode == self.freedv_mode: # Restore mode
|
||||||
QS.freedv_set_options(mode=index)
|
QS.freedv_set_options(mode=index)
|
||||||
|
if mode in ("Mode 2020", "Mode 2400A", "Mode 2400B"):
|
||||||
|
item.Enable(False)
|
||||||
if conf.button_layout == 'Large screen':
|
if conf.button_layout == 'Large screen':
|
||||||
b = QuiskCycleCheckbutton(frame, None, ('FDV-U', 'FDV-L'), is_radio=True)
|
b = QuiskCycleCheckbutton(frame, None, ('FDV-U', 'FDV-L'), is_radio=True)
|
||||||
b.idName = "FDV"
|
b.idName = "FDV"
|
||||||
@@ -5030,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:
|
||||||
@@ -5123,7 +5215,10 @@ class App(wx.App):
|
|||||||
elif mode in ('DGT-IQ', 'DGT-FM'):
|
elif mode in ('DGT-IQ', 'DGT-FM'):
|
||||||
center = 0
|
center = 0
|
||||||
elif mode in ('FDV-U', 'FDV-L'):
|
elif mode in ('FDV-U', 'FDV-L'):
|
||||||
center = max(1500, bandwidth // 2)
|
if bandwidth <= 3000:
|
||||||
|
center = 1500
|
||||||
|
else:
|
||||||
|
center = bandwidth // 2
|
||||||
elif mode in ('IMD',):
|
elif mode in ('IMD',):
|
||||||
center = 300 + bandwidth // 2
|
center = 300 + bandwidth // 2
|
||||||
else:
|
else:
|
||||||
@@ -5180,9 +5275,20 @@ class App(wx.App):
|
|||||||
self.multi_rx_screen.waterfall.pane2.filter_center = center
|
self.multi_rx_screen.waterfall.pane2.filter_center = center
|
||||||
if self.screen is self.filter_screen:
|
if self.screen is self.filter_screen:
|
||||||
self.screen.NewFilter()
|
self.screen.NewFilter()
|
||||||
|
def OnFreedvMonitor(self, event):
|
||||||
|
for item in self.freedv_menu.GetMenuItems():
|
||||||
|
if item.GetKind() == wx.ITEM_CHECK:
|
||||||
|
if item.IsChecked():
|
||||||
|
QS.set_params(freedv_monitor=1)
|
||||||
|
else:
|
||||||
|
QS.set_params(freedv_monitor=0)
|
||||||
|
break
|
||||||
def OnFreedvMenu(self, event):
|
def OnFreedvMenu(self, event):
|
||||||
text = ''
|
text = ''
|
||||||
for item in self.freedv_menu.GetMenuItems():
|
for item in self.freedv_menu.GetMenuItems():
|
||||||
|
kind = item.GetKind()
|
||||||
|
if kind != wx.ITEM_RADIO:
|
||||||
|
continue
|
||||||
if item.IsChecked():
|
if item.IsChecked():
|
||||||
text = item.GetItemLabel()
|
text = item.GetItemLabel()
|
||||||
break
|
break
|
||||||
@@ -5206,6 +5312,29 @@ class App(wx.App):
|
|||||||
else:
|
else:
|
||||||
self.freedv_menu_items[mode].Check(1)
|
self.freedv_menu_items[mode].Check(1)
|
||||||
print ("FreeDV change mode failed.")
|
print ("FreeDV change mode failed.")
|
||||||
|
self.OnChangeFilter()
|
||||||
|
def OnChangeFilter(self):
|
||||||
|
# Used by OnFreedvMenu because the FreeDV mode may change the filter sample rate
|
||||||
|
bw = int(self.filterButns.GetLabel())
|
||||||
|
# We can not use QS.get_filter_srate() because the FreeDV mode is not changed yet.
|
||||||
|
if self.freedv_mode in ("Mode 2400A", "Mode 2400B"):
|
||||||
|
frate = 48000;
|
||||||
|
else:
|
||||||
|
frate = 8000;
|
||||||
|
bw = min(bw, frate // 2)
|
||||||
|
self.filter_bandwidth = bw
|
||||||
|
center = self.GetFilterCenter(self.mode, bw)
|
||||||
|
filtI, filtQ = self.MakeFilterCoef(frate, None, bw, center)
|
||||||
|
lower_edge = center - bw // 2
|
||||||
|
QS.set_filters(filtI, filtQ, bw, lower_edge, 0)
|
||||||
|
self.multi_rx_screen.graph.filter_bandwidth = bw
|
||||||
|
self.multi_rx_screen.graph.filter_center = center
|
||||||
|
self.multi_rx_screen.waterfall.pane1.filter_bandwidth = bw
|
||||||
|
self.multi_rx_screen.waterfall.pane1.filter_center = center
|
||||||
|
self.multi_rx_screen.waterfall.pane2.filter_bandwidth = bw
|
||||||
|
self.multi_rx_screen.waterfall.pane2.filter_center = center
|
||||||
|
if self.screen is self.filter_screen:
|
||||||
|
self.screen.NewFilter()
|
||||||
def OnBtnHelp(self, event):
|
def OnBtnHelp(self, event):
|
||||||
if event.GetEventObject().GetValue():
|
if event.GetEventObject().GetValue():
|
||||||
self.OnBtnScreen(None, 'Help')
|
self.OnBtnScreen(None, 'Help')
|
||||||
@@ -5774,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')
|
||||||
@@ -6328,7 +6455,7 @@ class App(wx.App):
|
|||||||
Hardware.RepeaterOffset(0)
|
Hardware.RepeaterOffset(0)
|
||||||
QS.set_ctcss(0)
|
QS.set_ctcss(0)
|
||||||
QS.tx_hold_state(1)
|
QS.tx_hold_state(1)
|
||||||
if QS.is_key_down(): # Tx indicator
|
if QS.is_key_down() and not self.tx_inhibit: # Tx indicator
|
||||||
if not self.tx_indicator:
|
if not self.tx_indicator:
|
||||||
self.tx_indicator = True
|
self.tx_indicator = True
|
||||||
self.pttButton.Tx.TurnOn(True)
|
self.pttButton.Tx.TurnOn(True)
|
||||||
@@ -6389,6 +6516,8 @@ class App(wx.App):
|
|||||||
self.hot_key_ptt_active = False
|
self.hot_key_ptt_active = False
|
||||||
self.hot_key_ptt_was_down = self.hot_key_ptt_is_down
|
self.hot_key_ptt_was_down = self.hot_key_ptt_is_down
|
||||||
self.hot_key_ptt_pressed = False
|
self.hot_key_ptt_pressed = False
|
||||||
|
if self.tx_inhibit:
|
||||||
|
ptt = False
|
||||||
if ptt is True and not ptt_button_down:
|
if ptt is True and not ptt_button_down:
|
||||||
self.SetPTT(True)
|
self.SetPTT(True)
|
||||||
elif ptt is False and ptt_button_down:
|
elif ptt is False and ptt_button_down:
|
||||||
@@ -6459,6 +6588,16 @@ class App(wx.App):
|
|||||||
if self.timer - self.heart_time0 > 0.10: # call hardware to perform background tasks:
|
if self.timer - self.heart_time0 > 0.10: # call hardware to perform background tasks:
|
||||||
self.heart_time0 = self.timer
|
self.heart_time0 = self.timer
|
||||||
Hardware.HeartBeat()
|
Hardware.HeartBeat()
|
||||||
|
if conf.hermes_lite2_enable:
|
||||||
|
self.tx_inhibit = QS.get_params('quisk_tx_inhibit')
|
||||||
|
else:
|
||||||
|
self.tx_inhibit = 0
|
||||||
|
if self.tx_inhibit != self.old_tx_inhibit:
|
||||||
|
self.old_tx_inhibit = self.tx_inhibit
|
||||||
|
self.graph.display.Refresh()
|
||||||
|
self.waterfall.pane1.display.Refresh()
|
||||||
|
if self.tx_inhibit and self.spotButton.GetValue():
|
||||||
|
self.spotButton.SetValue(0, True)
|
||||||
msg = QS.GetQuiskPrintf()
|
msg = QS.GetQuiskPrintf()
|
||||||
if msg:
|
if msg:
|
||||||
print(msg, end='')
|
print(msg, end='')
|
||||||
@@ -6477,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():
|
||||||
|
|||||||
+49
-3
@@ -419,6 +419,8 @@ hermes_PTT_hang_time = 4
|
|||||||
## hermes_antenna_tuner Antenna tuner, text choice
|
## hermes_antenna_tuner Antenna tuner, text choice
|
||||||
# This option only applies to the Hermes Lite 2. Set this to None if you don't have a tuner.
|
# This option only applies to the Hermes Lite 2. Set this to None if you don't have a tuner.
|
||||||
# Set this to "Tune" to control the Icom AH-4 compatible ATU attached to the Hermes Lite 2 end plate.
|
# Set this to "Tune" to control the Icom AH-4 compatible ATU attached to the Hermes Lite 2 end plate.
|
||||||
|
# Note that this option only applies to the HL2 end plate, not the IO board.
|
||||||
|
# A tuner attached to the IO board is controlled from a button on the last line of the screen.
|
||||||
# Then when the Spot button is pressed with a positive power, a tune request is sent to the ATU.
|
# Then when the Spot button is pressed with a positive power, a tune request is sent to the ATU.
|
||||||
# If the Spot button is pressed with a zero power level, the tuner is set to bypass mode.
|
# If the Spot button is pressed with a zero power level, the tuner is set to bypass mode.
|
||||||
# Changes are immediate (no need to restart).
|
# Changes are immediate (no need to restart).
|
||||||
@@ -471,6 +473,13 @@ hermes_iob_rxin = 'J10 available'
|
|||||||
#hermes_iob_rxin = 'HL2 Rx to J9'
|
#hermes_iob_rxin = 'HL2 Rx to J9'
|
||||||
#hermes_iob_rxin = 'Use J9 and J10'
|
#hermes_iob_rxin = 'Use J9 and J10'
|
||||||
|
|
||||||
|
## hermes_lite2_enable Hermes Lite2 protocol, boolean
|
||||||
|
# This turns on some of the protocol extensions for the Hermes Lite2 that are not part
|
||||||
|
# of the official Hermes Protocol 1. Currently this only enables Tx Inhibit (CN8).
|
||||||
|
# Changes are immediate (no need to restart).
|
||||||
|
hermes_lite2_enable = True
|
||||||
|
#hermes_lite2_enable = False
|
||||||
|
|
||||||
|
|
||||||
# These are known power meter calibration tables. This table is not present in the JSON settings file.
|
# These are known power meter calibration tables. This table is not present in the JSON settings file.
|
||||||
power_meter_std_calibrations = {}
|
power_meter_std_calibrations = {}
|
||||||
@@ -1075,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:
|
||||||
#
|
#
|
||||||
@@ -1646,6 +1663,15 @@ start_ssb_delay = 100
|
|||||||
# Changes are immediate (no need to restart).
|
# Changes are immediate (no need to restart).
|
||||||
maximum_tx_secs = 0
|
maximum_tx_secs = 0
|
||||||
|
|
||||||
|
## TxRxSilenceMsec Tx-Rx silence msec, integer
|
||||||
|
# When changing from transmit to receive a radio may receive either noise or the end of the transmission.
|
||||||
|
# This may be returned as received audio, and can be heard as a loud pop.
|
||||||
|
# This parameter plays silence for the specified number of milliseconds when changing from transmit to receive.
|
||||||
|
# Make it long enough to zero out the pop, but not too much longer.
|
||||||
|
# It is ignored when in full duplex mode.
|
||||||
|
# Changes are immediate (no need to restart).
|
||||||
|
TxRxSilenceMsec = 50
|
||||||
|
|
||||||
## keyupDelay Keyup delay msecs, integer
|
## keyupDelay Keyup delay msecs, integer
|
||||||
# This is the key hang time for semi-breakin CW. It is the time in milliseconds
|
# This is the key hang time for semi-breakin CW. It is the time in milliseconds
|
||||||
# from the last CW key release until changing to receive.
|
# from the last CW key release until changing to receive.
|
||||||
@@ -1759,7 +1785,7 @@ freedv_tx_msg = ''
|
|||||||
# This is the list of FreeDV modes and their index number. The starting mode is the first listed.
|
# This is the list of FreeDV modes and their index number. The starting mode is the first listed.
|
||||||
freedv_modes = (('Mode 1600', 0),
|
freedv_modes = (('Mode 1600', 0),
|
||||||
('Mode 2400A', 3), ('Mode 2400B', 4), ('Mode 800XA', 5),
|
('Mode 2400A', 3), ('Mode 2400B', 4), ('Mode 800XA', 5),
|
||||||
('Mode 700C', 6), ('Mode 700D', 7), ('Mode 2020', 8), ('Mode 700E', 13))
|
('Mode 700C', 6), ('Mode 700D', 7), ('Mode 700E', 13), ('Mode 2020', 8))
|
||||||
|
|
||||||
# These are the filter bandwidths for each mode. Quisk has built-in optimized filters
|
# These are the filter bandwidths for each mode. Quisk has built-in optimized filters
|
||||||
# for these values, but you can change them if you want.
|
# for these values, but you can change them if you want.
|
||||||
@@ -1770,7 +1796,7 @@ FilterBwFM = (8000, 10000, 12000, 16000, 18000, 20000)
|
|||||||
FilterBwIMD = FilterBwSSB
|
FilterBwIMD = FilterBwSSB
|
||||||
FilterBwDGT = (200, 400, 1500, 3200, 4800, 10000)
|
FilterBwDGT = (200, 400, 1500, 3200, 4800, 10000)
|
||||||
FilterBwEXT = (8000, 10000, 12000, 15000, 17000, 20000)
|
FilterBwEXT = (8000, 10000, 12000, 15000, 17000, 20000)
|
||||||
FilterBwFDV = (1500, 2000, 3000, '', '', '')
|
FilterBwFDV = (1500, 2000, 3000, '6000', '', '')
|
||||||
|
|
||||||
# If your hardware file defines the method OnButtonPTT(self, event), then Quisk will
|
# If your hardware file defines the method OnButtonPTT(self, event), then Quisk will
|
||||||
# display a PTT button you can press. The method must switch your hardware to
|
# display a PTT button you can press. The method must switch your hardware to
|
||||||
@@ -2085,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]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1549,6 +1549,8 @@ class QuiskMenu(wx.Menu):
|
|||||||
item.Check(is_checked)
|
item.Check(is_checked)
|
||||||
self.InitData(item, handler)
|
self.InitData(item, handler)
|
||||||
return item
|
return item
|
||||||
|
def AppendSeparator(self):
|
||||||
|
wx.Menu.AppendSeparator(self)
|
||||||
def IsItemChecked(self, item_text):
|
def IsItemChecked(self, item_text):
|
||||||
nid = self.item_text2id[item_text]
|
nid = self.item_text2id[item_text]
|
||||||
menu_item = self.FindItemById(nid)
|
menu_item = self.FindItemById(nid)
|
||||||
|
|||||||
@@ -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.28'
|
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"):
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ char * sound_format_names[4] = {"Int32", "Int16", "Float32", "Int24"} ;
|
|||||||
static struct wav_file file_rec_audio, file_rec_samples, file_rec_mic;
|
static struct wav_file file_rec_audio, file_rec_samples, file_rec_mic;
|
||||||
static int close_file_rec;
|
static int close_file_rec;
|
||||||
|
|
||||||
static 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;
|
||||||
|
|||||||
+30
-10
@@ -495,8 +495,37 @@ static snd_pcm_format_t check_formats(struct sound_dev * dev, snd_pcm_hw_params_
|
|||||||
{
|
{
|
||||||
snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
|
snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
|
||||||
dev->sample_bytes = 0;
|
dev->sample_bytes = 0;
|
||||||
|
#if 0
|
||||||
|
char * card_name;
|
||||||
|
int card;
|
||||||
|
snd_pcm_info_t * pcm_info;
|
||||||
|
printf("driver %s\n", snd_ctl_card_info_get_driver(info));
|
||||||
|
int loopback = 0;
|
||||||
|
if (snd_pcm_info_malloc(&pcm_info) == 0) {
|
||||||
|
if (snd_pcm_info (dev->handle, pcm_info) == 0) {
|
||||||
|
card = snd_pcm_info_get_card (pcm_info);
|
||||||
|
if (card >= 0) {
|
||||||
|
if (snd_card_get_name(card, &card_name) == 0) {
|
||||||
|
if (strcmp(card_name, "Loopback") == 0)
|
||||||
|
loopback = 1;
|
||||||
|
printf("name %s\n", card_name);
|
||||||
|
free(card_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snd_pcm_info_free(pcm_info);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
strMcpy (dev->msg1, "Available formats: ", QUISK_SC_SIZE);
|
strMcpy (dev->msg1, "Available formats: ", QUISK_SC_SIZE);
|
||||||
|
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S16) == 0) {
|
||||||
|
if (!dev->sample_bytes) {
|
||||||
|
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||||
|
dev->sample_bytes = 2;
|
||||||
|
dev->sound_format = Int16;
|
||||||
|
format = SND_PCM_FORMAT_S16;
|
||||||
|
}
|
||||||
|
strncat(dev->msg1, "S16 ", QUISK_SC_SIZE);
|
||||||
|
}
|
||||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S32) == 0) {
|
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S32) == 0) {
|
||||||
if (!dev->sample_bytes) {
|
if (!dev->sample_bytes) {
|
||||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||||
@@ -515,15 +544,6 @@ static snd_pcm_format_t check_formats(struct sound_dev * dev, snd_pcm_hw_params_
|
|||||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_U24) == 0) {
|
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_U24) == 0) {
|
||||||
strncat(dev->msg1, "U24 ", QUISK_SC_SIZE);
|
strncat(dev->msg1, "U24 ", QUISK_SC_SIZE);
|
||||||
}
|
}
|
||||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S16) == 0) {
|
|
||||||
if (!dev->sample_bytes) {
|
|
||||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
|
||||||
dev->sample_bytes = 2;
|
|
||||||
dev->sound_format = Int16;
|
|
||||||
format = SND_PCM_FORMAT_S16;
|
|
||||||
}
|
|
||||||
strncat(dev->msg1, "S16 ", QUISK_SC_SIZE);
|
|
||||||
}
|
|
||||||
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S24_3LE) == 0) {
|
if (snd_pcm_hw_params_test_format (dev->handle, hware, SND_PCM_FORMAT_S24_3LE) == 0) {
|
||||||
if (!dev->sample_bytes) {
|
if (!dev->sample_bytes) {
|
||||||
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
strncat(dev->msg1, "*", QUISK_SC_SIZE);
|
||||||
|
|||||||
Reference in New Issue
Block a user