Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71146c9eaf | ||
|
|
814e4ce08c | ||
|
|
36d717636c | ||
|
|
26773d5d09 | ||
|
|
15375aa4ea | ||
|
|
7f516653d5 |
@@ -1,3 +1,36 @@
|
|||||||
|
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.32
|
||||||
from .quisk import main
|
from .quisk import main
|
||||||
|
|||||||
+11
-5
@@ -1484,7 +1484,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 +1519,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 +2005,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_'):
|
||||||
@@ -3399,14 +3399,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."),
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1546,22 +1470,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
|
||||||
@@ -164,12 +176,8 @@ static void GetAddrs(void)
|
|||||||
handle_index = 2;
|
handle_index = 2;
|
||||||
GET_HANDLE2;
|
GET_HANDLE2;
|
||||||
if ( ! hLib) {
|
if ( ! hLib) {
|
||||||
handle_index = 3;
|
handle_index = 4;
|
||||||
GET_HANDLE3;
|
GET_HANDLE4;
|
||||||
if ( ! hLib) {
|
|
||||||
handle_index = 4;
|
|
||||||
GET_HANDLE4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hLib) {
|
if (hLib) {
|
||||||
@@ -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
|
||||||
|
|
||||||
#define FREEDV_MODE_1600 0
|
// available speech modes
|
||||||
#define FREEDV_MODE_700 1
|
#define FREEDV_MODE_1600 0
|
||||||
#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_700E 13
|
||||||
#define FREEDV_MODE_2020 8
|
#define FREEDV_MODE_2020 8
|
||||||
#define FREEDV_MODE_700E 13
|
#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 (last_rx_mode != (int)rxMode) { // change to sideband
|
if ( ! filtered) { // rxMode changed
|
||||||
last_rx_mode = rxMode;
|
quisk_filt_tune((struct quisk_dFilter *)&filter1, 3000.0 / 48000.0, rxMode != FDV_L);
|
||||||
if (rxMode == FDV_U) // upper sideband
|
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1500.0 / 8000.0, rxMode != FDV_L);
|
||||||
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,11 @@ 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 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 +182,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 +1847,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 +1923,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 +1946,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,40 +2151,100 @@ 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;
|
||||||
else
|
case 48000:
|
||||||
process_agc(&Agc2, cSamples, nSamples, 1);
|
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
|
||||||
|
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++) {
|
dd = fabs(creal(cBuf[i])) / CLIP16;
|
||||||
measure_audio_sum += dsamples[i] * dsamples[i];
|
if (dd > dmax_in)
|
||||||
measure_audio_count += 1;
|
dmax_in = dd;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// current coding assumes speech sample rate is only 8000 or 16000 - needs tweak for others
|
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
//printf("Max sample in %.0lf\n", dmax_in);
|
||||||
if( n_speech_sample_rate == 8000 )
|
dmax_in = 0;
|
||||||
|
}
|
||||||
|
#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_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||||
|
break;
|
||||||
|
case 16000:
|
||||||
|
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
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) {
|
||||||
@@ -2395,7 +2468,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 +2742,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 +2950,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 +3157,8 @@ static PyObject * get_params(PyObject * self, PyObject * args)
|
|||||||
return PyInt_FromLong(quisk_serial_ptt);
|
return PyInt_FromLong(quisk_serial_ptt);
|
||||||
if (strcmp(name, "hl2_txbuf_errors") == 0)
|
if (strcmp(name, "hl2_txbuf_errors") == 0)
|
||||||
return PyInt_FromLong(hl2_txbuf_errors);
|
return PyInt_FromLong(hl2_txbuf_errors);
|
||||||
|
if (strcmp(name, "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 +3188,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 +3762,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) {
|
||||||
@@ -4629,6 +4707,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;
|
||||||
}
|
}
|
||||||
@@ -5786,6 +5867,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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -1762,6 +1762,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)
|
||||||
@@ -3497,6 +3503,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:
|
||||||
@@ -3762,7 +3770,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()
|
||||||
@@ -4704,11 +4713,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"
|
||||||
@@ -5123,7 +5136,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 +5196,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 +5233,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')
|
||||||
@@ -6328,7 +6378,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 +6439,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 +6511,14 @@ 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()
|
||||||
msg = QS.GetQuiskPrintf()
|
msg = QS.GetQuiskPrintf()
|
||||||
if msg:
|
if msg:
|
||||||
print(msg, end='')
|
print(msg, end='')
|
||||||
|
|||||||
+20
-2
@@ -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 = {}
|
||||||
@@ -1646,6 +1655,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 +1777,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 +1788,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
|
||||||
|
|||||||
@@ -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.32'
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ 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 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;
|
||||||
|
|||||||
+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