Quisk version 4.2.29

This commit is contained in:
James C. Ahlstrom
2024-01-11 12:31:13 -05:00
parent 15375aa4ea
commit 26773d5d09
20 changed files with 361 additions and 316 deletions
+9
View File
@@ -1,3 +1,12 @@
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!
+85 -161
View File
@@ -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>
+1 -1
View File
@@ -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,
+37 -63
View File
@@ -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;
+22 -10
View File
@@ -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.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+63 -46
View File
@@ -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;
}
+94 -29
View File
@@ -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
@@ -1843,6 +1845,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};
@@ -2138,40 +2141,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) {
@@ -2877,7 +2940,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 +3176,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;
+3 -2
View File
@@ -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;
+42 -1
View File
@@ -4704,11 +4704,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 +5127,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 +5187,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 +5224,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')
+2 -2
View File
@@ -1759,7 +1759,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 +1770,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
+2
View File
@@ -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)
+1 -1
View File
@@ -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.29'
fp = open("__init__.py", "w") # write title string
fp.write("#Quisk version %s\n" % Version)