Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36d717636c | ||
|
|
26773d5d09 | ||
|
|
15375aa4ea | ||
|
|
7f516653d5 |
@@ -1,3 +1,19 @@
|
||||
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
|
||||
===================================
|
||||
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
|
||||
**December 26, 2023**
|
||||
**December 27, 2023**
|
||||
|
||||
This is Quisk, a Software Defined Radio (SDR).
|
||||
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 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.30
|
||||
from .quisk import main
|
||||
|
||||
+1
-1
@@ -2005,7 +2005,7 @@ class ControlMixin:
|
||||
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',
|
||||
'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'):
|
||||
setattr(conf, name, x)
|
||||
application.ImmediateChange(name)
|
||||
elif name[0:4] in ('lin_', 'win_'):
|
||||
|
||||
@@ -131,7 +131,7 @@ button on the lower right.
|
||||
<h2>News</h2>
|
||||
<p>
|
||||
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>
|
||||
<br>
|
||||
<p>
|
||||
@@ -189,23 +189,22 @@ Many others contributed to Quisk, and are mentioned in comments in the source co
|
||||
<h2 id="Installation">Installation</h2>
|
||||
<p>
|
||||
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>.
|
||||
It can be installed using the standard Python setup tools. See specific directions below.
|
||||
<a href="https://pypi.org">https://pypi.org</a> and it is available on github.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
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>
|
||||
If you want to install a more recent version, uninstall the old version first.
|
||||
<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 of Python, uninstall the old version first.
|
||||
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.
|
||||
</p>
|
||||
<br>
|
||||
<h3 id="g0.2.2">Windows Initial Installation</h3>
|
||||
<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.
|
||||
<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.
|
||||
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
|
||||
@@ -280,186 +279,111 @@ python -m pip uninstall quisk
|
||||
</p>
|
||||
<h3 id="g0.2.1">Linux Initial Installation</h3>
|
||||
<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.
|
||||
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
|
||||
version will work. On my Ubuntu machine, "python2" starts Python2 and "python3" starts Python3.
|
||||
Most likely both Python2 and Python3 are already installed on your computer.
|
||||
On my Ubuntu machine, "python2" starts Python2 and "python3" starts Python3.
|
||||
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.
|
||||
Install these packages. Some of these can be installed from Python using "pip",
|
||||
but it is better to use your package manager because pip and the package manager do not coordinate.
|
||||
<b>Python2 is obsolete</b>, so you should only install Quisk to Python3, and use "python3" to start it.
|
||||
Install these packages by using your package manager, not Python pip.
|
||||
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>
|
||||
</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>
|
||||
<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.
|
||||
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>
|
||||
</p>
|
||||
<h3>Linux Pip Installation</h3>
|
||||
<br>
|
||||
<p>
|
||||
You can use the Python program "pip" to install Quisk into the Python package directory. This is the easiest
|
||||
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>
|
||||
<b> Installation of Quisk using Python tools like pip is deprecated</b>.
|
||||
</p>
|
||||
<pre>
|
||||
sudo -H python3 -m pip uninstall quisk
|
||||
sudo -H python3 -m pip install --no-cache-dir --force-reinstall quisk
|
||||
</pre>
|
||||
<br>
|
||||
<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>
|
||||
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>
|
||||
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>
|
||||
<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>
|
||||
<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.
|
||||
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.
|
||||
<br>
|
||||
<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
|
||||
To uninstall Quisk, just delete the quisk directory.
|
||||
</p>
|
||||
<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>
|
||||
<p>
|
||||
<br>
|
||||
|
||||
@@ -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.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,
|
||||
0.002811674671240099, 0.001132278163758895, -0.001835394694865431, -0.004766409947196539, -0.005833007003128954, -0.003914015660218843,
|
||||
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);
|
||||
|
||||
/* advanced freedv open options required by some modes */
|
||||
struct freedv_advanced { // from freedv_api.h
|
||||
int interleave_frames;
|
||||
struct freedv_advanced {
|
||||
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
|
||||
@@ -66,11 +75,14 @@ static int handle_index = -1;
|
||||
// need access to these here and in quisk.c
|
||||
int n_speech_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 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{
|
||||
struct freedv * hFreedv;
|
||||
COMP * demod_in;
|
||||
short * demod_in;
|
||||
int rxdata_index;
|
||||
short speech_out[SPEECH_BUF_SIZE]; // output buffer
|
||||
int speech_available; // number of samples in output buffer
|
||||
@@ -164,12 +176,8 @@ static void GetAddrs(void)
|
||||
handle_index = 2;
|
||||
GET_HANDLE2;
|
||||
if ( ! hLib) {
|
||||
handle_index = 3;
|
||||
GET_HANDLE3;
|
||||
if ( ! hLib) {
|
||||
handle_index = 4;
|
||||
GET_HANDLE4;
|
||||
}
|
||||
handle_index = 4;
|
||||
GET_HANDLE4;
|
||||
}
|
||||
}
|
||||
if (hLib) {
|
||||
@@ -231,19 +239,17 @@ static void GetAddrs(void)
|
||||
return;
|
||||
}
|
||||
|
||||
static int quisk_freedv_rx(complex double * cSamples, 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.
|
||||
static int quisk_freedv_rx(short * sSamples, double * dsamples, int count, int bank) // Called from the sound thread.
|
||||
{ // 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
|
||||
// output data will be returned at n_speech_sample_rate with corresponding block sizes
|
||||
int i, nout, need, have, sync;
|
||||
int n_speech_samples;
|
||||
complex double cx;
|
||||
double scale = (double)CLIP32 / CLIP16; // convert 32 bits to 16 bits
|
||||
struct freedv * hF;
|
||||
struct _rx_channel * pCh;
|
||||
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++) {
|
||||
if (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;
|
||||
need = freedv_nin(hF);
|
||||
for (i = 0; i < count; i++) {
|
||||
cx = cRxFilterOut(cSamples[i], bank, 0);
|
||||
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->demod_in[pCh->rxdata_index] = sSamples[i];
|
||||
pCh->rxdata_index++;
|
||||
if (pCh->rxdata_index >= need) {
|
||||
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)
|
||||
sync = freedv_get_sync(hF);
|
||||
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++)
|
||||
dsamples[nout] = pCh->speech_out[nout] * scale * 0.7;
|
||||
dsamples[nout] = pCh->speech_out[nout] * ((double)CLIP32 / CLIP16 * 0.99);
|
||||
if (nout) {
|
||||
pCh->speech_available -= nout;
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
int i, nout;
|
||||
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;
|
||||
|
||||
if (dsamples == NULL) { // shutdown
|
||||
if (mod_out)
|
||||
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;
|
||||
return 0; // nothing to do
|
||||
}
|
||||
if ( ! rx_channel[0].hFreedv)
|
||||
return 0;
|
||||
@@ -371,13 +359,13 @@ static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int cou
|
||||
if( nRatio < 1 || nRatio > 6 )
|
||||
nRatio = 1;
|
||||
if (mod_out == NULL) { // initialize
|
||||
mod_out = (COMP *)malloc(sizeof(COMP) * n_nom_modem_samples);
|
||||
memset(mod_out, 0, sizeof(COMP) * n_nom_modem_samples);
|
||||
speech_in = (short*)malloc(sizeof(short) * n_speech_samples);
|
||||
mod_out = (COMP *)malloc(sizeof(COMP) * MAX_MODEM_SAMPLES);
|
||||
memset(mod_out, 0, sizeof(COMP) * MAX_MODEM_SAMPLES);
|
||||
speech_in = (short*)malloc(sizeof(short) * MAX_SPEECH_SAMPLES);
|
||||
speech_index=0;
|
||||
mod_index=0;
|
||||
real_mod_out = (short *)malloc(sizeof(short) * n_nom_modem_samples);
|
||||
memset(real_mod_out, 0, sizeof(short) * n_nom_modem_samples);
|
||||
real_mod_out = (short *)malloc(sizeof(short) * MAX_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 );
|
||||
}
|
||||
nout = 0;
|
||||
@@ -386,7 +374,7 @@ static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int cou
|
||||
if (speech_index >= n_speech_samples) {
|
||||
// Calculate a new block, but first write out the rest of the old block
|
||||
// 800xa uses real not complex
|
||||
if( freedv_current_mode == FREEDV_MODE_800XA ) {
|
||||
if( is_real ) {
|
||||
for ( ; mod_index < n_nom_modem_samples; mod_index++)
|
||||
cSamples[nout++] = real_mod_out[mod_index];
|
||||
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
|
||||
for( int j=0; j<nRatio; j++) {
|
||||
if (mod_index < n_nom_modem_samples ) {
|
||||
if( freedv_current_mode == FREEDV_MODE_800XA ) {
|
||||
if( is_real ) {
|
||||
cSamples[nout++] = real_mod_out[mod_index];
|
||||
}
|
||||
else {
|
||||
@@ -471,21 +459,15 @@ static void CloseFreedv(void) // Called from the GUI thread or sound thread
|
||||
freedv_close(rx_channel[i].hFreedv);
|
||||
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_tx(NULL, NULL, 0);
|
||||
quisk_freedv_tx(NULL, NULL, 0, 0);
|
||||
freedv_current_mode = -1;
|
||||
}
|
||||
|
||||
static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
||||
{
|
||||
int i, n_max_modem_samples;
|
||||
int i;
|
||||
struct freedv * hF;
|
||||
|
||||
if ( ! hLib)
|
||||
@@ -500,7 +482,7 @@ static int OpenFreedv(void) // Called from the GUI thread or sound thread
|
||||
break;
|
||||
case 3:
|
||||
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;
|
||||
default:
|
||||
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
|
||||
}
|
||||
}
|
||||
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) {
|
||||
struct freedv_advanced adv;
|
||||
memset(&adv, 0, sizeof(adv));
|
||||
adv.interleave_frames = interleave_frames;
|
||||
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;
|
||||
if (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) {
|
||||
CloseFreedv();
|
||||
requested_mode = -1;
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
extern int freedv_current_mode; // the current FreeDV running mode
|
||||
|
||||
#define FREEDV_MODE_1600 0
|
||||
#define FREEDV_MODE_700 1
|
||||
#define FREEDV_MODE_700B 2
|
||||
#define FREEDV_MODE_2400A 3
|
||||
#define FREEDV_MODE_2400B 4
|
||||
#define FREEDV_MODE_800XA 5
|
||||
#define FREEDV_MODE_700C 6
|
||||
#define FREEDV_MODE_700D 7
|
||||
#define FREEDV_MODE_2020 8
|
||||
#define FREEDV_MODE_700E 13
|
||||
// available speech modes
|
||||
#define FREEDV_MODE_1600 0
|
||||
#define FREEDV_MODE_2400A 3
|
||||
#define FREEDV_MODE_2400B 4
|
||||
#define FREEDV_MODE_800XA 5
|
||||
#define FREEDV_MODE_700C 6
|
||||
#define FREEDV_MODE_700D 7
|
||||
#define FREEDV_MODE_700E 13
|
||||
#define FREEDV_MODE_2020 8
|
||||
#define FREEDV_MODE_2020B 16
|
||||
|
||||
// available data modes
|
||||
#define FREEDV_MODE_FSK_LDPC 9
|
||||
#define FREEDV_MODE_DATAC1 10
|
||||
#define FREEDV_MODE_DATAC3 12
|
||||
#define FREEDV_MODE_DATAC0 14
|
||||
#define FREEDV_MODE_DATAC4 18
|
||||
#define FREEDV_MODE_DATAC13 19
|
||||
|
||||
// Sample rates used
|
||||
#define FREEDV_FS_8000 8000
|
||||
#define FREEDV_FS_16000 16000
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -168,6 +168,7 @@ static void modem_status(void)
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <termios.h>
|
||||
|
||||
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.
+63
-46
@@ -626,32 +626,32 @@ static int tx_filter_digital(complex double * filtered, int count)
|
||||
static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
||||
{ // Input samples are creal(filtered), output is filtered.
|
||||
// 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;
|
||||
// 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 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 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 modem_sample_rate of 8000 or 48000 sps depending on the mode, and output may be real or complex.
|
||||
double dtmp, magn, dsample;
|
||||
int modem_sample_rate, speech_sample_rate, use_filter, is_real;
|
||||
static int samples_size = 0;
|
||||
static int last_rx_mode;
|
||||
static int last_freedv_mode;
|
||||
static int do_init = 1;
|
||||
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
|
||||
static double time_long, time_short;
|
||||
static double inMax=0.3;
|
||||
static double * dsamples = NULL;
|
||||
static struct quisk_cFilter filter2;
|
||||
static struct quisk_dFilter filtDecim;
|
||||
static struct quisk_cFilter cfiltInterp;
|
||||
static struct quisk_cFilter filter1, filter2;
|
||||
static struct quisk_dFilter filtDecim48to16, filtDecim16to8;;
|
||||
static struct quisk_cFilter cfiltInterp8to48;
|
||||
|
||||
if (do_init) { // initialization
|
||||
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
|
||||
do_init = 0;
|
||||
last_rx_mode = -1;
|
||||
last_freedv_mode = -1;
|
||||
memset(&filter2, 0, sizeof(filter2));
|
||||
quisk_filt_dInit(&filtDecim, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // pass 3000, stop 4000
|
||||
quisk_filt_cInit(&cfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
||||
quisk_filt_cInit(&filter1, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // at 48000 sps pass 3000 stop 4000 times two plus 3000
|
||||
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // at 8000 sps pass 1500 stop 1800 times two plus 1500
|
||||
quisk_filt_cInit(&cfiltInterp8to48, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
||||
quisk_filt_dInit(&filtDecim48to16, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double)); // pass 6000 stop 8000
|
||||
quisk_filt_dInit(&filtDecim16to8, quiskFilt16dec8Coefs, sizeof(quiskFilt16dec8Coefs)/sizeof(double)); // pass 3000 stop 4000
|
||||
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
|
||||
Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression
|
||||
@@ -660,36 +660,38 @@ static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
||||
bbb = -2.0 * aaa * Xmax;
|
||||
ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax;
|
||||
}
|
||||
if (last_freedv_mode != freedv_current_mode) { // correct filter2 depends on freedv mode
|
||||
last_freedv_mode = freedv_current_mode;
|
||||
last_rx_mode = -1; // re-tune new filter
|
||||
if (filter2.cSamples)
|
||||
free(filter2.cSamples);
|
||||
filter2.cSamples = NULL;
|
||||
if (filter2.cpxCoefs)
|
||||
free(filter2.cpxCoefs);
|
||||
filter2.cpxCoefs = NULL;
|
||||
if (filter2.cBuf)
|
||||
free(filter2.cBuf);
|
||||
filter2.cBuf = NULL;
|
||||
switch(freedv_current_mode) {
|
||||
case FREEDV_MODE_700D: // filter2 is not used
|
||||
switch (freedv_current_mode) { // n_modem_sample_rate and n_speech_sample_rate may not be valid
|
||||
case FREEDV_MODE_2400A:
|
||||
case FREEDV_MODE_2400B:
|
||||
use_filter = 1; // center frequency 3000
|
||||
is_real = 1;
|
||||
modem_sample_rate = 48000;
|
||||
speech_sample_rate = 8000;
|
||||
break;
|
||||
case FREEDV_MODE_2020:
|
||||
use_filter = 0;
|
||||
is_real = 0;
|
||||
modem_sample_rate = 8000;
|
||||
speech_sample_rate = 16000;
|
||||
break;
|
||||
case FREEDV_MODE_800XA:
|
||||
use_filter = 2; // center frequency 1500
|
||||
is_real = 1;
|
||||
modem_sample_rate = 8000;
|
||||
speech_sample_rate = 8000;
|
||||
break;
|
||||
// TODO: add modes that return real samples that must be converted to complex
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (last_rx_mode != (int)rxMode) { // change to sideband
|
||||
last_rx_mode = rxMode;
|
||||
if (rxMode == FDV_U) // upper sideband
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / n_modem_sample_rate, 1);
|
||||
else if (rxMode == FDV_L) // lower sideband
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / n_modem_sample_rate, 0);
|
||||
}
|
||||
if ( ! filtered)
|
||||
if ( ! filtered) { // rxMode changed
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter1, 3000.0 / 48000.0, rxMode != FDV_L);
|
||||
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1500.0 / 8000.0, rxMode != FDV_L);
|
||||
return 0;
|
||||
}
|
||||
// check size of dsamples[] buffer
|
||||
if (count > samples_size) {
|
||||
samples_size = count * 2;
|
||||
@@ -701,12 +703,25 @@ static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
||||
for (i = 0; i < count; i++)
|
||||
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)
|
||||
if (quisk_sound_state.mic_sample_rate == 48000)
|
||||
count = quisk_dDecimate(dsamples, count, &filtDecim, quisk_sound_state.mic_sample_rate / n_speech_sample_rate);
|
||||
else if (quisk_sound_state.mic_sample_rate == 8000 && n_speech_sample_rate != 8000)
|
||||
if (quisk_sound_state.mic_sample_rate == 48000) {
|
||||
switch (speech_sample_rate) {
|
||||
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");
|
||||
}
|
||||
// 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_short = 1.0 - exp(- dtmp / 0.005);
|
||||
for (i = 0; i < count; i++) {
|
||||
@@ -730,13 +745,15 @@ static int tx_filter_freedv(complex double * filtered, int count, int encode)
|
||||
}
|
||||
//QuiskWavWriteD(&hWav, dsamples, count);
|
||||
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);
|
||||
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);
|
||||
// Interpolate up to 48000
|
||||
if (MIC_OUT_RATE != n_modem_sample_rate )
|
||||
count = quisk_cInterpolate(filtered, count, &cfiltInterp, MIC_OUT_RATE / n_modem_sample_rate);
|
||||
if (MIC_OUT_RATE != modem_sample_rate )
|
||||
count = quisk_cInterpolate(filtered, count, &cfiltInterp8to48, MIC_OUT_RATE / modem_sample_rate);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <complex.h> // Use native C99 complex type for fftw3
|
||||
#include <fftw3.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
#include <winsock2.h>
|
||||
@@ -107,7 +108,7 @@ static PyObject * pyApp; // Application instance
|
||||
static int fft_size; // size of fft, e.g. 1024
|
||||
int data_width; // number of points to return as graph data; fft_size * n
|
||||
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 quiskTxHoldState; // hold Tx until the repeater frequency shift is complete
|
||||
int quisk_is_vna; // zero for normal program, one for the VNA program
|
||||
@@ -150,6 +151,7 @@ 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 hl2_txbuf_errors; // errors in the Hermes-Lite2 Tx buffer
|
||||
static int hl2_txbuf_state; // state machine for errors in the Hermes-Lite2 Tx buffer
|
||||
static int freedv_monitor; // pass the Freedv audio to the speakers instead of to the demodulator
|
||||
|
||||
static complex double PySampleBuf[SAMP_BUFFER_SIZE]; // buffer for samples returned from Python
|
||||
static int PySampleCount; // count of samples in buffer
|
||||
@@ -179,6 +181,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_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 TxRxSilenceMsec = 50; // Play silence in msec on Tx to Rx
|
||||
static int key_is_down = 0; // internal key state up or down
|
||||
|
||||
static double agcReleaseGain=80; // AGC maximum gain
|
||||
@@ -1843,6 +1846,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)
|
||||
{ // Changes here will require changes to get_filter_rate();
|
||||
int i;
|
||||
bool isLSB;
|
||||
complex double cx;
|
||||
double d, di, dd;
|
||||
static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0};
|
||||
@@ -1918,6 +1922,10 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
if(bank == 0)
|
||||
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_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
@@ -1937,6 +1945,10 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
if(bank == 0)
|
||||
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_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand6);
|
||||
nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7);
|
||||
@@ -2138,40 +2150,100 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples
|
||||
}
|
||||
break;
|
||||
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();
|
||||
// added conditional for 2400A/B mmodes
|
||||
// current coding assumes modem sample rate is 8000 16000 or 48000
|
||||
if( n_modem_sample_rate <= 16000 )
|
||||
// current coding assumes input rate n_modem_sample_rate is 8000 or 48000.
|
||||
// decimate:
|
||||
switch (n_modem_sample_rate) {
|
||||
case 8000:
|
||||
quisk_filter_srate = quisk_decim_srate / 6;
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3);
|
||||
if (bank == 0)
|
||||
process_agc(&Agc1, cSamples, nSamples, 1);
|
||||
else
|
||||
process_agc(&Agc2, cSamples, nSamples, 1);
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
|
||||
break;
|
||||
case 48000:
|
||||
quisk_filter_srate = quisk_decim_srate;
|
||||
break;
|
||||
default:
|
||||
quisk_filter_srate = quisk_decim_srate / 6;
|
||||
QuiskPrintf("Bad FreeDV modem rate in quisk_process_demodulate\n");
|
||||
break;
|
||||
}
|
||||
// filter and demodulate to mono audio:
|
||||
isLSB = (rx_mode == FDV_L);
|
||||
complex double cBuf[nSamples];
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
cx = cRxFilterOut(cSamples[i], bank, nFilter);
|
||||
if (isLSB)
|
||||
dd = creal(cx) + cimag(cx);
|
||||
else
|
||||
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
|
||||
if (quisk_decim_srate != 48000) {
|
||||
dd = quisk_decim_srate / 48000.0;
|
||||
nSamples = cFracDecim(cSamples, nSamples, dd);
|
||||
nSamples = cFracDecim(cBuf, nSamples, dd);
|
||||
quisk_decim_srate = 48000;
|
||||
}
|
||||
quisk_filter_srate = n_speech_sample_rate;
|
||||
// added conditional for 2400A/B mmodes
|
||||
if( n_modem_sample_rate == 8000 )
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2);
|
||||
if (bank == 0)
|
||||
process_agc(&Agc1, cBuf, nSamples, 0);
|
||||
else
|
||||
process_agc(&Agc2, cBuf, nSamples, 0);
|
||||
// pass data decimated down to n_modem_sample_rate
|
||||
// return data is as n_speech_sample_rate
|
||||
if (pt_quisk_freedv_rx)
|
||||
nSamples = (* pt_quisk_freedv_rx)(cSamples, dsamples, nSamples, bank);
|
||||
if(bank == 0) {
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
measure_audio_sum += dsamples[i] * dsamples[i];
|
||||
measure_audio_count += 1;
|
||||
}
|
||||
// return data is at n_speech_sample_rate
|
||||
#if 0
|
||||
static double dmax_in = 0;
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
dd = fabs(creal(cBuf[i])) / CLIP16;
|
||||
if (dd > dmax_in)
|
||||
dmax_in = dd;
|
||||
}
|
||||
// current coding assumes speech sample rate is only 8000 or 16000 - needs tweak for others
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||
if( n_speech_sample_rate == 8000 )
|
||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||
//printf("Max sample in %.0lf\n", dmax_in);
|
||||
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);
|
||||
break;
|
||||
case 16000:
|
||||
nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) {
|
||||
@@ -2395,7 +2467,7 @@ int quisk_process_samples(complex double * cSamples, int nSamples)
|
||||
quisk_sound_state.sample_rate;
|
||||
nout = (int)dOutCounter; // number of samples to output
|
||||
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;
|
||||
if (quisk_active_sidetone == 2 && QUISK_CWKEY_DOWN) { // Play sidetone instead of radio for CW
|
||||
if (! sidetoneIsOn) { // turn on sidetone
|
||||
@@ -2877,7 +2949,7 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args)
|
||||
break;
|
||||
case FDV_U: // digital voice
|
||||
case FDV_L:
|
||||
filter_srate = n_speech_sample_rate;
|
||||
filter_srate = n_modem_sample_rate;
|
||||
break;
|
||||
}
|
||||
//QuiskPrintf("Filter rate %d\n", filter_srate);
|
||||
@@ -3113,13 +3185,15 @@ static PyObject * read_fftw_wisdom(PyObject * self, PyObject * args)
|
||||
static PyObject * set_params(PyObject * self, PyObject * args, PyObject * keywds)
|
||||
{ /* Call with keyword arguments ONLY; change local parameters */
|
||||
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;
|
||||
|
||||
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,
|
||||
&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;
|
||||
if (nbytes != -1) {
|
||||
py_sample_rx_bytes = nbytes;
|
||||
@@ -4629,6 +4703,9 @@ static PyObject * ImmediateChange(PyObject * self, PyObject * args) // called fr
|
||||
else if ( ! strcmp(name, "maximum_tx_secs")) {
|
||||
maximum_tx_secs = QuiskGetConfigInt(name, 0);
|
||||
}
|
||||
else if ( ! strcmp(name, "TxRxSilenceMsec")) {
|
||||
TxRxSilenceMsec = QuiskGetConfigInt(name, 50);
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
@@ -5786,6 +5863,7 @@ static PyObject * record_app(PyObject * self, PyObject * args)
|
||||
quisk_start_cw_delay = QuiskGetConfigInt("start_cw_delay", 15);
|
||||
quisk_start_ssb_delay = QuiskGetConfigInt("start_ssb_delay", 100);
|
||||
maximum_tx_secs = QuiskGetConfigInt("maximum_tx_secs", 0);
|
||||
TxRxSilenceMsec = QuiskGetConfigInt("TxRxSilenceMsec", 50);
|
||||
quisk_sound_state.sample_rate = rate;
|
||||
fft_sample_rate = rate;
|
||||
is_little_endian = 1; // Test machine byte order
|
||||
|
||||
@@ -315,6 +315,7 @@ extern play_state_t quisk_play_state; // startup, receiving, sidetone
|
||||
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_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_ssb_delay; // milliseconds to discard output for all modes except CW
|
||||
extern struct sound_dev * quiskPlaybackDevices[]; // array of Playback sound devices
|
||||
@@ -404,8 +405,8 @@ void quisk_set_play_state(void);
|
||||
void quisk_poll_hardware_key(void);
|
||||
|
||||
// Functions supporting digital voice codecs
|
||||
typedef int (* ty_dvoice_codec_rx)(complex double *, double *, int, int);
|
||||
typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int);
|
||||
typedef int (* ty_dvoice_codec_rx)(short *, double *, int, 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_tx pt_quisk_freedv_tx;
|
||||
|
||||
|
||||
@@ -3762,7 +3762,8 @@ class App(wx.App):
|
||||
# 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.
|
||||
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
|
||||
self.graph_width = wFrame - (graph.width - graph.graph_width) # less graph borders equals actual graph_width
|
||||
graph.Destroy()
|
||||
@@ -4704,11 +4705,15 @@ class App(wx.App):
|
||||
self.freedv_menu = QuiskMenu("freedv_menu")
|
||||
msg = conf.freedv_tx_msg
|
||||
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:
|
||||
item = self.freedv_menu.AppendRadioItem(mode, self.OnFreedvMenu, mode == self.freedv_mode)
|
||||
self.freedv_menu_items[index] = item
|
||||
if mode == self.freedv_mode: # Restore mode
|
||||
QS.freedv_set_options(mode=index)
|
||||
if mode in ("Mode 2020", "Mode 2400A", "Mode 2400B"):
|
||||
item.Enable(False)
|
||||
if conf.button_layout == 'Large screen':
|
||||
b = QuiskCycleCheckbutton(frame, None, ('FDV-U', 'FDV-L'), is_radio=True)
|
||||
b.idName = "FDV"
|
||||
@@ -5123,7 +5128,10 @@ class App(wx.App):
|
||||
elif mode in ('DGT-IQ', 'DGT-FM'):
|
||||
center = 0
|
||||
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',):
|
||||
center = 300 + bandwidth // 2
|
||||
else:
|
||||
@@ -5180,9 +5188,20 @@ class App(wx.App):
|
||||
self.multi_rx_screen.waterfall.pane2.filter_center = center
|
||||
if self.screen is self.filter_screen:
|
||||
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):
|
||||
text = ''
|
||||
for item in self.freedv_menu.GetMenuItems():
|
||||
kind = item.GetKind()
|
||||
if kind != wx.ITEM_RADIO:
|
||||
continue
|
||||
if item.IsChecked():
|
||||
text = item.GetItemLabel()
|
||||
break
|
||||
@@ -5206,6 +5225,29 @@ class App(wx.App):
|
||||
else:
|
||||
self.freedv_menu_items[mode].Check(1)
|
||||
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):
|
||||
if event.GetEventObject().GetValue():
|
||||
self.OnBtnScreen(None, 'Help')
|
||||
|
||||
+13
-2
@@ -419,6 +419,8 @@ hermes_PTT_hang_time = 4
|
||||
## 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.
|
||||
# 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.
|
||||
# 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).
|
||||
@@ -1646,6 +1648,15 @@ start_ssb_delay = 100
|
||||
# Changes are immediate (no need to restart).
|
||||
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
|
||||
# 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.
|
||||
@@ -1759,7 +1770,7 @@ freedv_tx_msg = ''
|
||||
# This is the list of FreeDV modes and their index number. The starting mode is the first listed.
|
||||
freedv_modes = (('Mode 1600', 0),
|
||||
('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
|
||||
# for these values, but you can change them if you want.
|
||||
@@ -1770,7 +1781,7 @@ FilterBwFM = (8000, 10000, 12000, 16000, 18000, 20000)
|
||||
FilterBwIMD = FilterBwSSB
|
||||
FilterBwDGT = (200, 400, 1500, 3200, 4800, 10000)
|
||||
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
|
||||
# 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)
|
||||
self.InitData(item, handler)
|
||||
return item
|
||||
def AppendSeparator(self):
|
||||
wx.Menu.AppendSeparator(self)
|
||||
def IsItemChecked(self, item_text):
|
||||
nid = self.item_text2id[item_text]
|
||||
menu_item = self.FindItemById(nid)
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
# You must define the version here. A title string including
|
||||
# the version will be written to __init__.py and read by quisk.py.
|
||||
|
||||
Version = '4.2.28'
|
||||
Version = '4.2.30'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
|
||||
Reference in New Issue
Block a user