Quisk version 4.2.53
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
Quisk Version 4.2.53 June 2026
|
||||
===============================
|
||||
I added TCI support for Software Defined Connectors CW skimmer. Please test.
|
||||
|
||||
This version of Quisk now works on Python 3.14 and higher. I changed to a new build system because
|
||||
the old one has been deprecated by Python. They no longer support running setup.py.
|
||||
The Linux version has a new makefile. The Windows version is installed by pip as usual.
|
||||
Please test.
|
||||
|
||||
Quisk Version 4.2.52 May 2026
|
||||
==============================
|
||||
This is a fix for various TCI bugs. Please test with WSJT-X and let me know if all problems are solved.
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.52
|
||||
#Quisk version 4.2.53
|
||||
from .quisk import main
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <Python.h>
|
||||
#include <complex.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifdef MS_WINDOWS
|
||||
#include <Winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <string.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -105,7 +106,7 @@ static PyObject * open_rx_udp(const char * ip, int port)
|
||||
wVersionRequested = MAKEWORD(2, 2);
|
||||
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
|
||||
sprintf(buf, "Failed to initialize Winsock (WSAStartup)");
|
||||
return PyString_FromString(buf);
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
#endif
|
||||
// quisk_using_udp = 1;
|
||||
@@ -145,7 +146,7 @@ static PyObject * open_rx_udp(const char * ip, int port)
|
||||
else {
|
||||
sprintf(buf, "Failed to open socket");
|
||||
}
|
||||
return PyString_FromString(buf);
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
|
||||
static PyObject * close_rx_udp(PyObject * self, PyObject * args)
|
||||
@@ -343,7 +344,7 @@ static PyObject * open_samples(PyObject * self, PyObject * args)
|
||||
quisk_sample_source(NULL, NULL, &afedri_read_rx_udp);
|
||||
//////////////
|
||||
return open_rx_udp(ip, port); // AFEDRI specific
|
||||
// return PyString_FromString(buf); // return a string message
|
||||
// return PyUnicode_FromString(buf); // return a string message
|
||||
}
|
||||
|
||||
// Miscellaneous functions needed by the SDR-IQ; called from the GUI thread as
|
||||
|
||||
+21
-4
@@ -1,7 +1,24 @@
|
||||
MODULE_NAME := afedrinet_io
|
||||
OBJDIR := ../build/local
|
||||
|
||||
afedrinet2:
|
||||
python2 setup.py build_ext --force --inplace
|
||||
CFLAGS := $(shell python3 ../makefil.py --cflags)
|
||||
LDFLAGS := $(shell python3 ../makefil.py --ldflags)
|
||||
EXTENSION_SUFFIX := $(shell python3 ../makefil.py --suffix)
|
||||
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
|
||||
|
||||
afedrinet3:
|
||||
# Default target: Link the shared library object
|
||||
$(TARGET): afedrinet_io.o $(OBJDIR)/import_quisk_api.o $(OBJDIR)/is_key_down.o
|
||||
$(CC) -o $@ afedrinet_io.o $(LDFLAGS) $(OBJDIR)/import_quisk_api.o $(OBJDIR)/is_key_down.o
|
||||
|
||||
# Compile the C source files into object files
|
||||
afedrinet_io.o: afedrinet_io.c ../quisk.h
|
||||
$(CC) -o $@ -c $< $(CFLAGS) -I..
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -f *.o
|
||||
-rm -f $(MODULE_NAME)*.so
|
||||
|
||||
.PHONY: afedrinet-old
|
||||
afedrinet-old:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ h3 {font-size:1.6rem;}
|
||||
<div class="contents">
|
||||
<h1>
|
||||
<a id="Welcome"></a>
|
||||
Welcome to QUISK (September 2025)
|
||||
Welcome to QUISK (June 2026)
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
@@ -197,7 +197,7 @@ Quisk is free open source software written in Python and C. It is hosted on the
|
||||
<br>
|
||||
<p>
|
||||
If you have Python 3 installed on your computer, just continue to use that version.
|
||||
<b>On Windows Quisk requires Python 3.8, 3.9, 3.10, 3.11, 3.12 or 3.13.</b>
|
||||
On Windows Quisk requires Python 3.8 or higher.
|
||||
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.
|
||||
<b>A Quisk installation is needed for each version of Python you have. If you upgrade to a newer Python
|
||||
@@ -208,8 +208,7 @@ you must re-install Quisk. But your Quisk settings will be preserved.</b>
|
||||
<p>
|
||||
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>On Windows Quisk requires Python 3.8, 3.9, 3.10, 3.11, 3.12 or 3.13.</b>
|
||||
There may be newer versions of Python, but until support is added, use the versions listed above.
|
||||
On Windows Quisk requires Python 3.8 or higher.
|
||||
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
|
||||
this option so that you can start python by just typing "python" instead of the whole path
|
||||
@@ -383,6 +382,7 @@ You only clone github for the initial installation. To update Quisk, use pull fo
|
||||
<pre>
|
||||
cd ~/quisk
|
||||
git pull
|
||||
make clean
|
||||
make
|
||||
</pre>
|
||||
<br>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Python.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <complex.h>
|
||||
#include "quisk.h"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Python.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <complex.h> // Use native C99 complex type for fftw3
|
||||
#include "quisk.h"
|
||||
#include "filter.h"
|
||||
|
||||
@@ -32,6 +32,8 @@ with this:
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void ** Quisk_API; // array of pointers to functions and variables from module _quisk
|
||||
struct sound_conf * pt_quisk_sound_state; // pointer to quisk_sound_state
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Python.h> // used by quisk.h
|
||||
#include <complex.h> // Used by quisk.h
|
||||
#include <string.h>
|
||||
#include "quisk.h"
|
||||
|
||||
// This module provides methods to access the state of the key.
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
# This provides helper functions for our makefile.
|
||||
|
||||
# Changes for MacOS support thanks to Mario, DL3LSM.
|
||||
# Changes for building from macports provided by Eric, KM4DSJ
|
||||
# Updated code for a Mac build contributed by Christoph, DL1YCF, December 2020.
|
||||
# Converted from setup.py to makefile by Jim Ahlstrom, June 2026
|
||||
|
||||
import sys, os, subprocess, sysconfig
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit(2)
|
||||
|
||||
job = sys.argv[1]
|
||||
|
||||
def flags ():
|
||||
# Fetch compiler flags and link flags from Python
|
||||
result = subprocess.run(["python3-config", "--cflags"], capture_output=True, text=True, check=True)
|
||||
cflags = result.stdout.strip()
|
||||
cflags += " -fPIC"
|
||||
result = subprocess.run(["python3-config", "--ldflags"], capture_output=True, text=True, check=True)
|
||||
ldflags = result.stdout.strip()
|
||||
ldflags += " -shared -lm -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -lfftw3"
|
||||
if sys.platform == "darwin": # Build for Macintosh
|
||||
cflags += " -DQUISK_HAVE_PORTAUDIO "
|
||||
ldflags += " -lportaudio "
|
||||
if os.path.isdir('/opt/local/include'): # MacPorts
|
||||
base_dir = '/opt/local'
|
||||
elif os.path.isdir('/usr/local/include'): # HomeBrew on macOS Intel
|
||||
base_dir = '/usr/local'
|
||||
elif os.path.isdir('/opt/homebrew/include'): # HomeBrew on Apple Silicon
|
||||
base_dir = '/opt/homebrew'
|
||||
else: # Regular build?
|
||||
base_dir = '/usr'
|
||||
if os.path.isfile(base_dir + "/include/pulse/pulseaudio.h"):
|
||||
cflags += " -DQUISK_HAVE_PULSEAUDIO "
|
||||
ldflags += " -lpulse "
|
||||
cflags += " -I. -I%s/include " % base_dir
|
||||
ldflags += " -L. -L%s/lib " % base_dir
|
||||
elif "freebsd" in sys.platform: #Build for FreeBSD
|
||||
cflags += " -DQUISK_HAVE_PULSEAUDIO -I. -I/usr/local/include"
|
||||
ldflags += " -lpulse -L. -L/usr/local/lib"
|
||||
else: # Linux
|
||||
cflags += " -DQUISK_HAVE_ALSA -DQUISK_HAVE_PULSEAUDIO"
|
||||
ldflags += " -lasound -lpulse"
|
||||
if os.path.isfile("/usr/include/portaudio.h"):
|
||||
cflags += " -DQUISK_HAVE_PORTAUDIO"
|
||||
ldflags += " -lportaudio"
|
||||
return cflags, ldflags
|
||||
|
||||
if job == "--soapy":
|
||||
# See if soapy is available
|
||||
if os.path.isdir("/usr/include/SoapySDR") or os.path.isdir("/usr/local/include/SoapySDR"):
|
||||
print('1')
|
||||
else:
|
||||
print('0')
|
||||
|
||||
elif job == "--suffix":
|
||||
# Determine the platform-specific extension suffix (e.g., .cpython-311-x86_64-linux-gnu.so)
|
||||
print(sysconfig.get_config_var('EXT_SUFFIX'))
|
||||
|
||||
elif job == "--cflags":
|
||||
cc, ld = flags()
|
||||
print(cc)
|
||||
|
||||
elif job == "--ldflags":
|
||||
cc, ld = flags()
|
||||
print(ld)
|
||||
|
||||
else:
|
||||
sys.exit(1)
|
||||
@@ -1,18 +1,93 @@
|
||||
.PHONY: quisk
|
||||
# This makefile replaces the old Python "setup.py build_ext" which has been deprecated.
|
||||
# See "quisk-old" below to make Quisk using the old setup.py.
|
||||
|
||||
quisk:
|
||||
MODULE_NAME := _quisk
|
||||
OBJDIR := build/local
|
||||
SUBDIRS := afedrinet
|
||||
|
||||
ifeq ($(shell python3 makefil.py --soapy), 1)
|
||||
SUBDIRS := $(SUBDIRS) soapypkg
|
||||
endif
|
||||
|
||||
SRCS := \
|
||||
base64.c \
|
||||
extdemod.c \
|
||||
filter.c \
|
||||
freedv.c \
|
||||
handshake.c \
|
||||
is_key_down.c \
|
||||
microphone.c \
|
||||
quisk.c \
|
||||
quisk_wdsp.c \
|
||||
sha1.c \
|
||||
sound.c\
|
||||
sound_alsa.c \
|
||||
sound_directx.c \
|
||||
sound_portaudio.c \
|
||||
sound_pulseaudio.c \
|
||||
sound_wasapi.c \
|
||||
tci.c \
|
||||
utf8.c \
|
||||
utility.c \
|
||||
ws.c
|
||||
|
||||
CFLAGS := $(shell python3 makefil.py --cflags)
|
||||
LDFLAGS := $(shell python3 makefil.py --ldflags)
|
||||
EXTENSION_SUFFIX := $(shell python3 makefil.py --suffix)
|
||||
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
|
||||
|
||||
#$(info Info CFLAGS $(CFLAGS))
|
||||
#$(info Info LDFLAGS $(LDFLAGS))
|
||||
#$(info Info TARGET $(TARGET))
|
||||
#$(info Info SUBDIRS $(SUBDIRS))
|
||||
|
||||
OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(SRCS))
|
||||
|
||||
.PHONY: all clean test
|
||||
.PHONY: $(SUBDIRS)
|
||||
|
||||
# Default target
|
||||
all: $(OBJDIR) $(OBJDIR)/import_quisk_api.o ac2yd/remote.o $(TARGET) $(SUBDIRS)
|
||||
|
||||
$(SUBDIRS):
|
||||
$(MAKE) -C $@
|
||||
|
||||
$(OBJDIR):
|
||||
@mkdir -p $(OBJDIR)
|
||||
|
||||
# Link the shared library object
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $(OBJS) ac2yd/remote.o $(LDFLAGS)
|
||||
|
||||
# Define the location for dependency files
|
||||
DEPS = $(patsubst %.o,$(OBJDIR)/%.d,$(notdir $(OBJS)))
|
||||
DEPFLAGS = -MMD -MP -MF $(OBJDIR)/$(notdir $*).d
|
||||
-include $(DEPS)
|
||||
|
||||
# Compile the C source files into object files
|
||||
ac2yd/remote.o: ac2yd/remote.c quisk.h filter.h
|
||||
$(CC) -o $@ -c $< $(CFLAGS) $(DEPFLAGS)
|
||||
|
||||
$(OBJDIR)/import_quisk_api.o: import_quisk_api.c
|
||||
$(CC) -o $@ -c $< $(CFLAGS) $(DEPFLAGS)
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) -o $@ -c $< $(CFLAGS) $(DEPFLAGS)
|
||||
|
||||
clean:
|
||||
-rm -f $(OBJDIR)/*.o $(OBJDIR)/*.d
|
||||
-rm -f ac2yd/*.o
|
||||
-rm -f $(MODULE_NAME)*.so
|
||||
for dir in $(SUBDIRS); do \
|
||||
$(MAKE) -C $$dir clean; \
|
||||
done
|
||||
|
||||
# Run a test script to verify that the module can be imported
|
||||
test: $(TARGET)
|
||||
@python3 -c "import $(MODULE_NAME); print('Successfully imported:', $(MODULE_NAME).__file__)"
|
||||
|
||||
|
||||
|
||||
.PHONY: quisk-old
|
||||
quisk-old:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
@echo
|
||||
|
||||
quisk3:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
@echo
|
||||
|
||||
soapy3:
|
||||
(cd soapypkg; make soapy3)
|
||||
|
||||
afedrinet3:
|
||||
(cd afedrinet; make afedrinet3)
|
||||
|
||||
perseus3:
|
||||
(cd perseuspkg; make perseus3)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
#include <complex.h>
|
||||
#include <string.h>
|
||||
#include <fftw3.h>
|
||||
#include "quisk.h"
|
||||
#include <sys/types.h>
|
||||
|
||||
+21
-3
@@ -1,7 +1,25 @@
|
||||
MODULE_NAME := perseus
|
||||
OBJDIR := ../build/local
|
||||
|
||||
perseus2:
|
||||
python2 setup.py build_ext --force --inplace
|
||||
CFLAGS := $(shell python3 ../makefil.py --cflags)
|
||||
LDFLAGS := $(shell python3 ../makefil.py --ldflags)
|
||||
EXTENSION_SUFFIX := $(shell python3 ../makefil.py --suffix)
|
||||
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
|
||||
|
||||
perseus3:
|
||||
# Default target: Link the shared library object
|
||||
$(TARGET): perseus.o $(OBJDIR)/import_quisk_api.o
|
||||
$(CC) -o $@ perseus.o $(LDFLAGS) $(OBJDIR)/import_quisk_api.o
|
||||
|
||||
# Compile the C source files into object files
|
||||
perseus.o: perseus.c ../quisk.h ../filter.h
|
||||
$(CC) -o $@ -c $< $(CFLAGS) -I..
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -f *.o
|
||||
-rm -f $(TARGET)
|
||||
|
||||
.PHONY: perseus-old
|
||||
perseus-old:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
|
||||
|
||||
@@ -297,11 +297,11 @@ static PyObject * open_device(PyObject * self, PyObject * args)
|
||||
|
||||
|
||||
main_cleanup:
|
||||
return PyString_FromString("ERROR");
|
||||
return PyUnicode_FromString("ERROR");
|
||||
|
||||
exit_success:
|
||||
|
||||
return PyString_FromString(buf128);
|
||||
return PyUnicode_FromString(buf128);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "quisk"
|
||||
version = "4.2.53"
|
||||
requires-python = ">=3.13"
|
||||
description = 'QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.'
|
||||
license = "GPL-2.0-or-later"
|
||||
license-files = ["license.txt"]
|
||||
authors = [
|
||||
{name = "James C. Ahlstrom", email="jahlstr@gmail.com"}
|
||||
]
|
||||
keywords = ['quisk', 'sdr', 'software defined radio']
|
||||
classifiers = [
|
||||
'Development Status :: 6 - Mature',
|
||||
'Environment :: X11 Applications',
|
||||
'Environment :: Win32 (MS Windows)',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: C',
|
||||
'Topic :: Communications :: Ham Radio',
|
||||
]
|
||||
|
||||
[project.gui-scripts]
|
||||
quisk = "quisk.quisk:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://james.ahlstrom.name/quisk/index.html"
|
||||
Documentation = "https://james.ahlstrom.name/quisk/docs.html"
|
||||
Repository = "https://github.com/jimahlstrom/quisk"
|
||||
Changelog = "https://james.ahlstrom.name/quisk/CHANGELOG.txt"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <complex.h> // Use native C99 complex type for fftw3
|
||||
#include <string.h>
|
||||
#include <fftw3.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
@@ -4027,7 +4028,7 @@ static PyObject * open_rx_udp(PyObject * self, PyObject * args)
|
||||
wVersionRequested = MAKEWORD(2, 2);
|
||||
if (WSAStartup(wVersionRequested, &wsaData) != 0) {
|
||||
sprintf(buf, "Failed to initialize Winsock (WSAStartup)");
|
||||
return PyString_FromString(buf);
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
else {
|
||||
cleanupWSA = 1;
|
||||
@@ -4076,7 +4077,7 @@ static PyObject * open_rx_udp(PyObject * self, PyObject * args)
|
||||
else {
|
||||
sprintf(buf, "Failed to open socket");
|
||||
}
|
||||
return PyString_FromString(buf);
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
|
||||
static PyObject * open_sound(PyObject * self, PyObject * args)
|
||||
@@ -4290,7 +4291,7 @@ static PyObject * mixer_set(PyObject * self, PyObject * args)
|
||||
return NULL;
|
||||
|
||||
quisk_alsa_mixer_set(card_name, numid, value, err_msg, QUISK_SC_SIZE);
|
||||
return PyString_FromString(err_msg);
|
||||
return PyUnicode_FromString(err_msg);
|
||||
}
|
||||
|
||||
static PyObject * pc_to_hermes(PyObject * self, PyObject * args)
|
||||
@@ -4493,12 +4494,12 @@ static PyObject * ip_interfaces(PyObject * self, PyObject * args)
|
||||
baddr |= ~netmask;
|
||||
tup = PyTuple_New(4);
|
||||
if (name == NULL)
|
||||
PyTuple_SetItem(tup, 0, PyString_FromString("unnamed"));
|
||||
PyTuple_SetItem(tup, 0, PyUnicode_FromString("unnamed"));
|
||||
else
|
||||
PyTuple_SetItem(tup, 0, PyString_FromString(name));
|
||||
PyTuple_SetItem(tup, 1, PyString_FromString(Win_NtoA(ipAddr)));
|
||||
PyTuple_SetItem(tup, 2, PyString_FromString(Win_NtoA(netmask)));
|
||||
PyTuple_SetItem(tup, 3, PyString_FromString(Win_NtoA(baddr)));
|
||||
PyTuple_SetItem(tup, 0, PyUnicode_FromString(name));
|
||||
PyTuple_SetItem(tup, 1, PyUnicode_FromString(Win_NtoA(ipAddr)));
|
||||
PyTuple_SetItem(tup, 2, PyUnicode_FromString(Win_NtoA(netmask)));
|
||||
PyTuple_SetItem(tup, 3, PyUnicode_FromString(Win_NtoA(baddr)));
|
||||
PyList_Append(pylist, tup);
|
||||
Py_DECREF(tup);
|
||||
}
|
||||
@@ -4517,10 +4518,10 @@ static PyObject * ip_interfaces(PyObject * self, PyObject * args)
|
||||
while(p) {
|
||||
if ((p->ifa_addr) && p->ifa_addr->sa_family == AF_INET) {
|
||||
tup = PyTuple_New(4);
|
||||
PyTuple_SetItem(tup, 0, PyString_FromString(p->ifa_name));
|
||||
PyTuple_SetItem(tup, 1, PyString_FromString(Lin_NtoA(p->ifa_addr)));
|
||||
PyTuple_SetItem(tup, 2, PyString_FromString(Lin_NtoA(p->ifa_netmask)));
|
||||
PyTuple_SetItem(tup, 3, PyString_FromString(Lin_NtoA(p->ifa_broadaddr)));
|
||||
PyTuple_SetItem(tup, 0, PyUnicode_FromString(p->ifa_name));
|
||||
PyTuple_SetItem(tup, 1, PyUnicode_FromString(Lin_NtoA(p->ifa_addr)));
|
||||
PyTuple_SetItem(tup, 2, PyUnicode_FromString(Lin_NtoA(p->ifa_netmask)));
|
||||
PyTuple_SetItem(tup, 3, PyUnicode_FromString(Lin_NtoA(p->ifa_broadaddr)));
|
||||
PyList_Append(pylist, tup);
|
||||
Py_DECREF(tup);
|
||||
}
|
||||
@@ -5660,7 +5661,7 @@ static PyObject * Xdft(PyObject * pyseq, int inverse, int window)
|
||||
static fftw_complex * samples; // complex data for fft
|
||||
static fftw_plan planF, planB; // fft plan for fftW
|
||||
static double * fft_window; // window function
|
||||
Py_complex pycx; // Python C complex value
|
||||
complex double pycx;
|
||||
|
||||
if (PySequence_Check(pyseq) != 1) {
|
||||
PyErr_SetString (QuiskError, "DFT input data is not a sequence");
|
||||
@@ -5695,22 +5696,20 @@ static PyObject * Xdft(PyObject * pyseq, int inverse, int window)
|
||||
for (i = 0; i < size; i++) {
|
||||
obj = PySequence_GetItem(pyseq, j);
|
||||
if (PyComplex_Check(obj)) {
|
||||
pycx = PyComplex_AsCComplex(obj);
|
||||
pycx = PyComplex_RealAsDouble(obj) + I * PyComplex_ImagAsDouble(obj);
|
||||
}
|
||||
else if (PyFloat_Check(obj)) {
|
||||
pycx.real = PyFloat_AsDouble(obj);
|
||||
pycx.imag = 0;
|
||||
pycx = PyFloat_AsDouble(obj);
|
||||
}
|
||||
else if (PyInt_Check(obj)) {
|
||||
pycx.real = PyInt_AsLong(obj);
|
||||
pycx.imag = 0;
|
||||
pycx = PyInt_AsLong(obj);
|
||||
}
|
||||
else {
|
||||
Py_XDECREF(obj);
|
||||
PyErr_SetString (QuiskError, "DFT input data is not a complex/float/int number");
|
||||
return NULL;
|
||||
}
|
||||
samples[i] = pycx.real + I * pycx.imag;
|
||||
samples[i] = pycx;
|
||||
if (++j >= size)
|
||||
j = 0;
|
||||
Py_XDECREF(obj);
|
||||
@@ -5736,9 +5735,7 @@ static PyObject * Xdft(PyObject * pyseq, int inverse, int window)
|
||||
pyseq = PyList_New(fft_size);
|
||||
j = (size - 1) / 2; // zero frequency in input
|
||||
for (i = 0; i < fft_size; i++) {
|
||||
pycx.real = creal(samples[i]);
|
||||
pycx.imag = cimag(samples[i]);
|
||||
PyList_SetItem(pyseq, j, PyComplex_FromCComplex(pycx));
|
||||
PyList_SetItem(pyseq, j, PyComplex_FromDoubles(creal(samples[i]), cimag(samples[i])));
|
||||
if (++j >= size)
|
||||
j = 0;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#define QUISK_CWKEY_DOWN (quisk_hardware_cwkey || quisk_serial_key_down || quisk_midi_cwkey || quisk_remote_cwkey)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef MS_WINDOWS
|
||||
#define QUISK_SHUT_RD SD_RECEIVE
|
||||
#define QUISK_SHUT_BOTH SD_BOTH
|
||||
@@ -264,6 +266,7 @@ extern void send_graph_data(double * fft_avg, int fft_size, double zoom, double
|
||||
PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * keywds);
|
||||
PyObject * quisk_tci_get_params(PyObject * self, PyObject * args);
|
||||
void tci_send_audio(complex double * cSamples, int nSamples);
|
||||
void tci_send_iq(complex double * cSamples, int nSamples);
|
||||
int tci_get_mic(complex double * cSamples, int mic_count);
|
||||
extern uint64_t tci_tx_audio_client; // This is ws_cli_conn_t defined in ws.h.
|
||||
|
||||
|
||||
@@ -4030,7 +4030,9 @@ class App(wx.App):
|
||||
self.file_name_play_cq = ''
|
||||
self.midiControls = {} # Control object and associated function for Midi control name
|
||||
self.tci_vfo = 0
|
||||
self.tci_started = False
|
||||
self.tci_dds = 0
|
||||
self.tci_modulation = ''
|
||||
self.tci_split_enable = 0
|
||||
# get the screen size - thanks to Lucian Langa
|
||||
x, y, self.screen_width, self.screen_height = wx.Display().GetGeometry() # Using display index 0
|
||||
self.Bind(wx.EVT_IDLE, self.OnIdle)
|
||||
@@ -4361,12 +4363,12 @@ class App(wx.App):
|
||||
value = getattr(conf, "pulse_audio_verbose_output")
|
||||
QS.tci_set_params(verbose=value)
|
||||
QS.tci_set_params(start=1);
|
||||
self.tci_started = True
|
||||
QS.tci_set_params(tci_dds=self.VFO)
|
||||
QS.tci_set_params(tci_if=self.txFreq)
|
||||
self.tci_vfo = self.txFreq + self.VFO
|
||||
self.tci_dds = self.VFO
|
||||
QS.tci_set_params(tci_dds=self.tci_dds)
|
||||
self.tci_vfo = self.rxFreq + self.VFO
|
||||
QS.tci_set_params(tci_vfo=self.tci_vfo)
|
||||
QS.tci_set_params(tci_modulation=self.mode)
|
||||
self.tci_modulation = self.mode
|
||||
QS.tci_set_params(tci_modulation=self.tci_modulation)
|
||||
QS.tci_set_params(tci_trx=0)
|
||||
QS.tci_set_params(tci_split_enable=0)
|
||||
return True
|
||||
@@ -4693,6 +4695,8 @@ class App(wx.App):
|
||||
if msg:
|
||||
print(msg, end='')
|
||||
QS.close_key()
|
||||
if QS.tci_get_params("tci_clients_count") > 0:
|
||||
QS.tci_set_params(close=1)
|
||||
QS.set_file_name(record_button=0) # Turn off file recording
|
||||
time.sleep(0.1)
|
||||
if self.sound_thread:
|
||||
@@ -5782,7 +5786,6 @@ class App(wx.App):
|
||||
QS.set_sidetone(self.sidetone_volume, self.sidetone_0to1, self.ritFreq, conf.keyupDelay)
|
||||
def OnBtnSplit(self, event): # Called when the Split check button is pressed
|
||||
self.split_rxtx = self.splitButton.GetValue()
|
||||
QS.tci_set_params(tci_split_enable=self.split_rxtx)
|
||||
if self.split_rxtx:
|
||||
if self.split_offset == 0:
|
||||
if self.mode in ("CWL", "CWU"):
|
||||
@@ -5796,7 +5799,7 @@ class App(wx.App):
|
||||
QS.set_split_rxtx(0)
|
||||
self.split_offset = self.txFreq - self.rxFreq
|
||||
self.txFreq = self.rxFreq
|
||||
self.ChangeHwFrequency(self.txFreq, self.VFO, 'OnSplit', event=event)
|
||||
self.ChangeHwFrequency(self.txFreq, self.VFO, 'OnSplit', event=event, rx_freq=self.rxFreq)
|
||||
self.screen.SetTxFreq(self.txFreq, self.rxFreq)
|
||||
def OnMenuSplitPlay1(self, event):
|
||||
self.split_rxtx_play = 1
|
||||
@@ -6168,7 +6171,6 @@ class App(wx.App):
|
||||
self.ChangeRxTxFrequency(self.rxFreq + delta + self.VFO, self.txFreq + delta + self.VFO)
|
||||
Hardware.ChangeMode(mode)
|
||||
self.mode = mode
|
||||
QS.tci_set_params(tci_modulation=mode)
|
||||
self.MakeFilterButtons(self.Mode2Filters(mode))
|
||||
QS.set_rx_mode(Mode2Index.get(mode, 3))
|
||||
if mode == 'CWL':
|
||||
@@ -6829,22 +6831,56 @@ class App(wx.App):
|
||||
if self.timer - self.clip_time0 > 1.0:
|
||||
self.clip_time0 = 0
|
||||
self.freqDisplay.Clip(0)
|
||||
if QS.tci_get_params("tci_clients_count") > 0:
|
||||
# These are change requests from TCI clients. All must be echoed with the new value from Quisk.
|
||||
# modulation
|
||||
mode = QS.tci_get_params("tci_modulation")
|
||||
if mode is not None: # TCI set new modulation
|
||||
self.modeButns.SetLabel(mode, True)
|
||||
self.tci_modulation = self.mode
|
||||
QS.tci_set_params(tci_modulation=self.tci_modulation)
|
||||
# split
|
||||
split = QS.tci_get_params("tci_split_enable")
|
||||
if split is not None: # TCI set split_enable
|
||||
self.splitButton.SetValue(split, True)
|
||||
self.tci_split_enable = self.split_rxtx
|
||||
QS.tci_set_params(tci_split_enable=self.tci_split_enable)
|
||||
# tci dds == self.VFO
|
||||
tci_dds = QS.tci_get_params("tci_dds")
|
||||
if tci_dds is not None:
|
||||
self.ChangeHwFrequency(0, tci_dds, 'TCIrequest')
|
||||
self.tci_dds = self.VFO
|
||||
QS.tci_set_params(tci_dds=self.tci_dds)
|
||||
# tci_vfo == self.VFO + self.rxFreq
|
||||
tci_vfo = QS.tci_get_params("tci_vfo")
|
||||
if tci_vfo is not None:
|
||||
self.ChangeRxTxFrequency(tci_vfo, None)
|
||||
self.tci_vfo = self.VFO + self.rxFreq
|
||||
QS.tci_set_params(tci_vfo=self.tci_vfo)
|
||||
# tci_trx handled elsewhere
|
||||
if self.timer - self.heart_time0 > 0.10: # call hardware to perform background tasks:
|
||||
self.heart_time0 = self.timer
|
||||
Hardware.HeartBeat()
|
||||
if self.tci_started:
|
||||
if self.tci_vfo != self.txFreq + self.VFO: # limit the speed of frequency updates
|
||||
self.tci_vfo = self.txFreq + self.VFO
|
||||
if QS.tci_get_params("tci_clients_count") > 0: # limit the speed of frequency updates
|
||||
# these are changes made in Quisk that must be broadcast to all clients.
|
||||
# modulation
|
||||
if self.tci_modulation != self.mode:
|
||||
self.tci_modulation = self.mode
|
||||
QS.tci_set_params(tci_modulation=self.tci_modulation)
|
||||
# split
|
||||
if self.tci_split_enable != self.split_rxtx:
|
||||
self.tci_split_enable = self.split_rxtx
|
||||
QS.tci_set_params(tci_split_enable=self.tci_split_enable)
|
||||
# tci dds == self.VFO
|
||||
if self.tci_dds != self.VFO:
|
||||
self.tci_dds = self.VFO
|
||||
QS.tci_set_params(tci_dds=self.tci_dds)
|
||||
# tci_vfo == self.VFO + self.rxFreq
|
||||
tci_vfo = self.VFO + self.rxFreq
|
||||
if self.tci_vfo != tci_vfo:
|
||||
self.tci_vfo = tci_vfo
|
||||
QS.tci_set_params(tci_vfo=self.tci_vfo)
|
||||
freq = QS.tci_get_params("tci_vfo")
|
||||
if freq is not None: # TCI frequency
|
||||
self.ChangeRxTxFrequency(freq, None)
|
||||
mode = QS.tci_get_params("tci_modulation")
|
||||
if mode is not None: # TCI mode
|
||||
self.modeButns.SetLabel(mode, True)
|
||||
split = QS.tci_get_params("tci_split_enable")
|
||||
if split is not None: # TCI split_enable
|
||||
self.splitButton.SetValue(split, True)
|
||||
# tci_trx handled elsewhere
|
||||
if self.is_HermesLite2:
|
||||
self.tx_inhibit = QS.get_params('quisk_tx_inhibit')
|
||||
else:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// This module provides C access to the WDSP SDR library.
|
||||
|
||||
#include <Python.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <complex.h>
|
||||
|
||||
#define CLIP32 2147483647
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -0,0 +1,569 @@
|
||||
# SDR-TRX Quisk Configuration File
|
||||
# This file is to integrate the function of control of the SDR-TRX wint Quisk and with WSJT-X.
|
||||
# This is for control of the SDR-TRX with using the UDP port port 12346. WSJT-X interfaces with this
|
||||
# locally using UDP on port 2237. Capital letters begin commands to the SDR-TRX from Quisk,
|
||||
# and lowercase letters are for commands coming from WSJT-X (through this Quisk interface).
|
||||
# This is for usinng the SDR-TRX without a 3.5 mm audio card to the soundcard of the computer,
|
||||
# and the WSJT-X software using UDP, and the data coming over UDP part 12345 too.
|
||||
# It requires the libldl/ directory from weakmon to be in the directory you run this from.
|
||||
# It also requires the WSJTXClass.py file from wsjtx_transceiver_interface to be in the lib directory
|
||||
# and the version of that class. I have supplied a pruned down versions of ft8.py and ft4.45, because we don't need much
|
||||
# other than FT8Send and FT4Send from those files. You need to edit transceiver_config.yml to have your callsign and grid, etc.
|
||||
|
||||
import socket
|
||||
import time
|
||||
import yaml
|
||||
import struct
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
from ft8 import FT8Send
|
||||
from ft4 import FT4Send
|
||||
from quisk_hardware_model import Hardware as BaseHardware
|
||||
import struct
|
||||
import collections
|
||||
import lib.WSJTXClass as WSJTXClass # To use encoders
|
||||
|
||||
sys.path.append(os.path.expandvars('$WEAKMON'))
|
||||
|
||||
DEBUG_ON = True
|
||||
|
||||
#
|
||||
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
|
||||
#
|
||||
|
||||
################ Receivers SdrTrx, The SdrTrx radio by Rob Frohne, KL7NA
|
||||
## hardware_file_name Hardware file path, rfile
|
||||
# This is the file that contains the control logic for each radio.
|
||||
#hardware_file_name = 'sdrtrxpkg/quisk_hardware.py'
|
||||
|
||||
## widgets_file_name Widget file path, rfile
|
||||
# This optional file adds additional controls for the radio.
|
||||
#widgets_file_name = ''
|
||||
|
||||
## si570_xtal_freq Si570 crystal frequency, integer
|
||||
# This is the Si570 startup frequency in Hz. 114.285MHz is the typical
|
||||
# value from the data sheet; you can use 'usbsoftrock calibrate' to find
|
||||
# the value for your device.
|
||||
si570_xtal_freq = 114285000
|
||||
|
||||
## rx_max_amplitude_correct Max ampl correct, number
|
||||
# If you get your I/Q samples from a sound card, you will need to correct the
|
||||
# amplitude and phase for inaccuracies in the analog hardware. The correction is
|
||||
# entered using the controls from the "Rx Phase" button on the config screen.
|
||||
# You must enter a positive number. This controls the range of the control.
|
||||
rx_max_amplitude_correct = 0.2
|
||||
|
||||
## rx_max_phase_correct Max phase correct, number
|
||||
# If you get your I/Q samples from a sound card, you will need to correct the
|
||||
# amplitude and phase for inaccuracies in the analog hardware. The correction is
|
||||
# entered using the controls from the "Rx Phase" button on the config screen.
|
||||
# You must enter a positive number. This controls the range of the control in degrees.
|
||||
rx_max_phase_correct = 10.0
|
||||
|
||||
|
||||
# SDR-TRX Hardware Control Class
|
||||
#
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
# These are "static variable substitutes" since python doesn't have them.
|
||||
# They are shared by all instances of the class and don't get redefinede each time a method is called.
|
||||
# Move these into the Hardware class.
|
||||
# Uncomment these lines if you wish to use Pulseaudio
|
||||
name_of_sound_play = "pulse"
|
||||
|
||||
# Radio's Frequency limits.
|
||||
radio_lower = 3500000
|
||||
radio_upper = 30000000
|
||||
PACKET_SIZE = 1444 # 4 bytes for packet number + 360 * 4 bytes for int32_t pairs
|
||||
# Using 16 bit samples, so 2 bytes per sample.
|
||||
|
||||
# Set the number of Hz the signal is tuned to above the center frequency to avoid 1/f noise.
|
||||
vfo_Center_Offset = 10000
|
||||
|
||||
tx_ready_wsjtx = False
|
||||
tx_ready_wsjtx_sent = False
|
||||
tx_now = False
|
||||
|
||||
# This is the open code for the WSJT server.
|
||||
ft8_encoder = FT8Send()
|
||||
ft4_encoder = FT4Send()
|
||||
|
||||
class Packet:
|
||||
def __init__(self, data):
|
||||
self.number = struct.unpack('<I', data[:4])[0]
|
||||
self.pairs = [struct.unpack('<hh', data[i:i+4]) for i in range(4, len(data), 4)]
|
||||
|
||||
class PacketQueue:
|
||||
def __init__(self):
|
||||
self.queue = collections.deque()
|
||||
|
||||
def add_packet(self, packet):
|
||||
self.queue.append(packet)
|
||||
self.queue = collections.deque(sorted(self.queue, key=lambda p: p.number))
|
||||
|
||||
def check_missing_packets(self):
|
||||
for i in range(1, len(self.queue)):
|
||||
if self.queue[i].number != self.queue[i-1].number + 1:
|
||||
avg_pairs = [((self.queue[i-1].pairs[j][0] + self.queue[i].pairs[j][0]) // 2,
|
||||
(self.queue[i-1].pairs[j][1] + self.queue[i].pairs[j][1]) // 2)
|
||||
for j in range(len(self.queue[i-1].pairs))]
|
||||
self.queue.insert(i, Packet(struct.pack('<I', self.queue[i-1].number + 1) +
|
||||
b''.join(struct.pack('<ii', pair[0], pair[1]) for pair in avg_pairs)))
|
||||
|
||||
def get_packets(self):
|
||||
packets = list(self.queue)
|
||||
self.queue.clear()
|
||||
return packets
|
||||
|
||||
def open(self):
|
||||
# Connection for WSJT-X and data
|
||||
self.PICO_UDP_IP = None # Will be set to the IP of the machine sending data
|
||||
self.COMMAND_UDP_PORT = 12346 # This is the port the Pico listens on for UDP commands coming from quisk.
|
||||
self.DATA_UDP_PORT = 12345 # This is the port the quisk listens on for UDP IQ data coming from the Pico W.
|
||||
self.BROADCAST_PORT = 12347 # This is the port the Pico gets our IP address from.-
|
||||
self.broadcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.broadcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self.broadcast_message = "Quisk"
|
||||
self.broadcast_sock.setblocking(False)
|
||||
self.isConnected = False
|
||||
self.command_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.command_sock.setblocking(False)
|
||||
self.data_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.data_sock.bind(('', self.DATA_UDP_PORT))
|
||||
print("Data socket bound to IP:", self.data_sock.getsockname()[0])
|
||||
self.data_sock.setblocking(False)
|
||||
self.establish_connection()
|
||||
|
||||
self.InitSamples(2, 0) # Two bytes per sample, little endian.
|
||||
self.queue = self.PacketQueue()
|
||||
time.sleep(2)
|
||||
# Poll for version. Should probably confirm the response on this.
|
||||
version = str(self.get_parameter("VER")) # The way the firmware is now, this sets it up to use the UART or UDP.
|
||||
# UART is used if get_parameter uses the UART, and UDP is used if it uses the UDP.
|
||||
print(version)
|
||||
# Return an informative message for the config screen
|
||||
t = str(version) + ". Capture from sound card %s." % self.conf.name_of_sound_capt
|
||||
configs_file = open('transceiver_config.yml', 'r')
|
||||
configs = yaml.load(configs_file, Loader=yaml.BaseLoader)
|
||||
|
||||
# Global variables
|
||||
self.callsign = configs['callsign']
|
||||
self.grid = configs['grid']
|
||||
self.current_msg = ''
|
||||
self.rx_callsign = ''
|
||||
self.mode = 'FT8'
|
||||
self.tx_freq = 1200
|
||||
|
||||
# Connection for WSJT-X
|
||||
WSJTX_UDP_IP = "127.0.0.1" # Assuming you are running WSJT-X on the same computer as Quisk...
|
||||
WSJTX_UDP_PORT = 2237
|
||||
self.wsjtx_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.wsjtx_sock.bind((WSJTX_UDP_IP, WSJTX_UDP_PORT))
|
||||
self.wsjtx_sock.setblocking(False)
|
||||
|
||||
self.StartSamples()
|
||||
return t
|
||||
|
||||
def close(self):
|
||||
# Called once to close the Hardware
|
||||
self.get_parameter("RX") # Make sure we are in RX mode.
|
||||
time.sleep(1) # Wait for RX to be set.
|
||||
self.StopSamples()
|
||||
self.command_sock.close()
|
||||
self.wsjtx_sock.close()
|
||||
self.broadcast_sock.close()
|
||||
|
||||
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
|
||||
# Called whenever quisk requests a frequency change.
|
||||
# This sends the FREQ command dto set the centre frequency of the radio,
|
||||
# and will also move the 'tune' frequency (the section within the RX passband
|
||||
# which is to be demodulated) if it falls outside the passband (+/- sample_rate/2).
|
||||
print("Setting VFO to %d, with tune to %d." % (vfo, tune))
|
||||
print("Setting VFO to %d." % vfo)
|
||||
if vfo < self.radio_lower:
|
||||
vfo = self.radio_lower
|
||||
print("Outside range! Setting to %d" % self.radio_lower)
|
||||
|
||||
if vfo > self.radio_upper:
|
||||
vfo = self.radio_upper
|
||||
print("Outside range! Setting to %d" % self.radio_upper)
|
||||
|
||||
# print("sample_rate =", sample_rate) # I believe sample_rate comes from quisk.
|
||||
# If the tune frequency is outside the RX bandwidth, set it to somewhere within that bandwidth.
|
||||
if tune > (vfo + sample_rate / 2) or tune < (vfo - sample_rate / 2):
|
||||
tune = vfo + self.vfo_Center_Offset
|
||||
print("Bringing tune frequency back into the RX bandwidth.")
|
||||
|
||||
# success = self.set_parameter("FREQ",str(vfo))
|
||||
self.set_parameter("FREQ", str(vfo))
|
||||
self.set_parameter("TX_FREQ", str(tune))
|
||||
|
||||
return tune, vfo
|
||||
|
||||
def OnButtonPTT(self, event):
|
||||
btn = event.GetEventObject()
|
||||
if btn.GetValue():
|
||||
self.get_parameter("TX")
|
||||
print("TX")
|
||||
else:
|
||||
self.get_parameter("RX")
|
||||
print("RX")
|
||||
return BaseHardware.OnButtonPTT(self, event)
|
||||
|
||||
|
||||
|
||||
def establish_connection(self):
|
||||
while not self.isConnected:
|
||||
print("isConnected is: ", self.isConnected)
|
||||
# Check if a connection is established
|
||||
# Wait for data to arrive and set PICO_UDP_IP to the sender's IP
|
||||
while self.PICO_UDP_IP is None: # This needs to be fixed. It continues to send broadcast packets.
|
||||
try:
|
||||
print("Sending broadcast message...")
|
||||
self.broadcast_sock.sendto(self.broadcast_message.encode(), ('<broadcast>', self.BROADCAST_PORT))
|
||||
time.sleep(1) # Wait for 1 second before sending the next broadcast message
|
||||
except Exception as e:
|
||||
print("Broadcast message did not work: {0}".format(e))
|
||||
break
|
||||
try:
|
||||
data, addr = self.data_sock.recvfrom(self.PACKET_SIZE) # Adjust the buffer size as needed
|
||||
self.PICO_UDP_IP = addr[0]
|
||||
print("PICO_UDP_IP set to:", self.PICO_UDP_IP)
|
||||
# if data == self.broadcast_message:
|
||||
self.isConnected = True
|
||||
except socket.error:
|
||||
if self.PICO_UDP_IP is not None:
|
||||
print("Connection established with Pico W at IP:", self.PICO_UDP_IP, "but exception occurred.")
|
||||
break # Exit the loop if self.PICO_UDP_IP is not None
|
||||
pass # No data available yet
|
||||
|
||||
def GetRxSamples(self):
|
||||
# Initialize packet counter and time marker
|
||||
# This routine is made for FT8 and FT4, where we need to send a packet every 15 seconds.
|
||||
# The Pico W sending the data has a sample rate of 48046 samples per second > 48 ks/s.
|
||||
# We made the packets so they carry 180 samples per packet. That gives 4000 packets
|
||||
# per 15 seconds. We discard any packets after that four times a minute at 59, 14, 29,
|
||||
# and 44 seconds. This should make four ticks per minute, and we should be able to ignore
|
||||
# # them if we are doing FT8 or FT4. Something else might be more optimum for other modes.
|
||||
|
||||
packet_counter = 0
|
||||
next_time_marker = 0
|
||||
send_packets = False
|
||||
|
||||
while True:
|
||||
# Check if we've reached the next time marker
|
||||
current_time = time.time()
|
||||
current_seconds = current_time % 60
|
||||
|
||||
# Define the time markers
|
||||
if 59 <= current_seconds < 60:
|
||||
next_time_marker = current_time + 1 - current_seconds
|
||||
send_packets = True
|
||||
elif 14 <= current_seconds < 15:
|
||||
next_time_marker = current_time + 1 - (current_seconds - 14)
|
||||
send_packets = True
|
||||
elif 29 <= current_seconds < 30:
|
||||
next_time_marker = current_time + 1 - (current_seconds - 29)
|
||||
send_packets = True
|
||||
elif 44 <= current_seconds < 45:
|
||||
next_time_marker = current_time + 1 - (current_seconds - 44)
|
||||
send_packets = True
|
||||
|
||||
# If we've reached the next time marker, reset the packet counter
|
||||
if current_time >= next_time_marker and send_packets:
|
||||
packet_counter = 0
|
||||
send_packets = False
|
||||
|
||||
try:
|
||||
data, addr = self.data_sock.recvfrom(self.PACKET_SIZE)
|
||||
self.no_data_repeat = 0
|
||||
except socket.error:
|
||||
# Send a broadcast message to the Pico W to get the IP address in case it has reset.
|
||||
self.broadcast_sock.sendto(self.broadcast_message.encode(), ('<broadcast>', self.BROADCAST_PORT))
|
||||
break # No more packets available
|
||||
|
||||
packet = self.Packet(data)
|
||||
self.queue.add_packet(packet)
|
||||
|
||||
# Check for missing packets and fill them in
|
||||
self.queue.check_missing_packets()
|
||||
|
||||
# Get the packets and pass them to the processing thread
|
||||
packets = self.queue.get_packets()
|
||||
|
||||
for packet in packets:
|
||||
data = b''.join(struct.pack('<hh', pair[0], pair[1]) for pair in packet.pairs)
|
||||
# if len(data) % 4 != 0: # Each I/Q pair is now 4 bytes (2 * 2 bytes)
|
||||
# self.GotReadError(self, DEBUG_ON, "Error: Mismatched number of I and Q samples")
|
||||
# continue
|
||||
# If we've already sent 4000 packets since the last time marker, discard the rest
|
||||
if packet_counter >= 4000:
|
||||
continue
|
||||
|
||||
self.AddRxSamples(data)
|
||||
packet_counter += 1
|
||||
|
||||
#
|
||||
# UDP comms functions, to communicate with the Raspberry Pi Pico W board used in the SDR-TRX.
|
||||
#
|
||||
|
||||
def get_parameter(self, string):
|
||||
string = string + "\n"
|
||||
self.command_sock.sendto(string.encode(), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
return self.get_argument()
|
||||
|
||||
def set_parameter(self, string, arg):
|
||||
string = string + "," + arg + "\r" + "\n"
|
||||
self.command_sock.sendto(string.encode(), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
print('arg is: ', arg)
|
||||
temp_arg = self.get_argument()
|
||||
print('temp_arg is: ', temp_arg)
|
||||
return True
|
||||
|
||||
def get_argument(self):
|
||||
self.command_sock.setblocking(True)
|
||||
try:
|
||||
data1 = self.command_sock.recv(1024)
|
||||
print('Received on line 178: ', data1)
|
||||
except Exception as e:
|
||||
print("Exception occurred: {0}".format(e))
|
||||
return -1
|
||||
self.command_sock.setblocking(False)
|
||||
if not data1.startswith(b'OK'):
|
||||
print('Received on line 185: ', data1)
|
||||
# Maybe we didn't catch an OK line?
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
data1 = self.command_sock.recv(1024)
|
||||
print('Received on line 190: ', data1)
|
||||
break
|
||||
except BlockingIOError:
|
||||
pass
|
||||
# Check to see if we have a comma in the string. If not, there is no argument.
|
||||
if data1.find(b',') == -1:
|
||||
return -1
|
||||
|
||||
data1 = data1.split(b',')[1].rstrip(b'\r\n')
|
||||
print("data1 =")
|
||||
print(data1)
|
||||
|
||||
# Check for the OK string. Should we wait for the OK string?
|
||||
try:
|
||||
data2 = self.command_sock.recv(1024)
|
||||
if data2.startswith(b'OK'):
|
||||
return data2
|
||||
except BlockingIOError:
|
||||
return -1
|
||||
|
||||
# We might want to integrate the UDP reads below into the stuff above.
|
||||
|
||||
# The code below is to interface WSJT-X with SDR-TRX.
|
||||
|
||||
def encode_ft8(self, msg):
|
||||
try:
|
||||
a77 = self.ft8_encoder.pack(msg, 1)
|
||||
symbols = self.ft8_encoder.make_symbols(a77)
|
||||
except Exception as e:
|
||||
print("FT8 encoder error, check message!")
|
||||
print(f"Exception type: {type(e).__name__}")
|
||||
print(f"Exception message: {str(e)}")
|
||||
symbols = None
|
||||
time.sleep(3)
|
||||
return symbols
|
||||
|
||||
def encode_ft4(self, msg):
|
||||
try:
|
||||
a77 = self.ft4_encoder.pack(msg, 1)
|
||||
symbols = self.ft4_encoder.make_symbols(a77)
|
||||
except:
|
||||
print("FT4 encoder error, check message!")
|
||||
symbols = None
|
||||
time.sleep(3)
|
||||
return symbols
|
||||
|
||||
def load_symbols(self, symbols):
|
||||
print("Load symbols into transmitter..")
|
||||
self.command_sock.sendto(b'm', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
count = 0
|
||||
for symbol in symbols:
|
||||
self.command_sock.sendto(struct.pack('>B', symbol), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
count += 1
|
||||
# Wait to avoid Arduino serial buffer overflow. This may not be needed with the Pico.
|
||||
if count % 50 == 0:
|
||||
time.sleep(0.05)
|
||||
self.command_sock.sendto(b'\0', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
resp = self.command_sock.recv(1)
|
||||
if resp == b'm':
|
||||
print("Load OK")
|
||||
else:
|
||||
print(resp)
|
||||
|
||||
def change_freq(self, new_freq):
|
||||
#global self.tx_freq
|
||||
self.command_sock.setblocking(True)
|
||||
print("Change TX frequency to:", new_freq)
|
||||
self.command_sock.sendto(b'o', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
self.command_sock.sendto(struct.pack('<H', new_freq), (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
time.sleep(0.05)
|
||||
resp = self.command_sock.recv(1)
|
||||
print("resp after changing offset is: ", resp)
|
||||
self.command_sock.setblocking(False)
|
||||
if resp == b'o':
|
||||
print("New freq OK")
|
||||
self.tx_freq = new_freq
|
||||
|
||||
def set_mode(self, new_mode): # New more robust protocol.
|
||||
#global mode
|
||||
if new_mode == 'FT8':
|
||||
self.command_sock.sendto(b'e', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
time.sleep(.05)
|
||||
resp = self.command_sock.read(1)
|
||||
print("resp = ", resp)
|
||||
if resp == b'e':
|
||||
self.mode = new_mode
|
||||
print("Switched to: {0}".format(new_mode))
|
||||
self.current_msg = ''
|
||||
return True
|
||||
elif new_mode == 'FT4':
|
||||
self.command_sock.sendto(b'f', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
time.sleep(0.05)
|
||||
resp = self.command_sock.recv(1)
|
||||
print("resp = ", resp)
|
||||
if resp == b'f':
|
||||
self.mode = new_mode
|
||||
self.current_msg = ''
|
||||
print("Switched to: {0}".format(new_mode))
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def new_msg(self, msg):
|
||||
#global self.current_msg
|
||||
#global self.mode
|
||||
if msg != self.current_msg:
|
||||
print("Message: {0}".format(msg))
|
||||
if 'FT8' in self.mode:
|
||||
symbols = self.encode_ft8(msg)
|
||||
else:
|
||||
symbols = self.encode_ft4(msg)
|
||||
if symbols.any():
|
||||
# symbols = [kk for kk in range(79)]
|
||||
self.load_symbols(symbols)
|
||||
self.current_msg = msg
|
||||
else:
|
||||
return
|
||||
else:
|
||||
time.sleep(0.005) # Do we need this?
|
||||
|
||||
def transmit(self):
|
||||
if False: # not current_msg:
|
||||
print("No previous message!")
|
||||
time.sleep(1)
|
||||
else:
|
||||
print("TX!")
|
||||
self.command_sock.sendto(b't', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
# self.tx_now = False
|
||||
|
||||
def check_time_window(self, utc_time):
|
||||
time_window = 15 if 'FT8' in self.mode else 7
|
||||
print("Time window: ", time_window) # Is correct for FT8 and FT4.
|
||||
rm = utc_time.second % time_window
|
||||
if rm > 1 and rm < time_window - 1: # Within one second of exact time for FT8.
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# See if HeartBeat will work for this:
|
||||
def HeartBeat(self): # Should be called at 10 Hz from the main.
|
||||
# Check transmitter is initialized
|
||||
#raise Exception("In HeartBeat")
|
||||
|
||||
# print("\n\nWait for transmitter ready...")
|
||||
if self.tx_ready_wsjtx == False:
|
||||
if self.tx_ready_wsjtx_sent == False:
|
||||
self.command_sock.sendto(b'r', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
time.sleep(0.05)
|
||||
self.tx_ready_wsjtx_sent = True
|
||||
while True:
|
||||
try:
|
||||
x = self.command_sock.recv(1)
|
||||
if x == b'r':
|
||||
print("Transmitter ready!")
|
||||
self.tx_ready_wsjtx = True
|
||||
break
|
||||
except BlockingIOError:
|
||||
pass
|
||||
if self.tx_ready_wsjtx == True:
|
||||
try:
|
||||
fileContent = self.wsjtx_sock.recv(1024)
|
||||
except Exception as e:
|
||||
if e == BlockingIOError: # This is our way of pollig the socket.
|
||||
# Maybe use select to stop the errors printing after we quit.
|
||||
return BaseHardware.HeartBeat(self)
|
||||
else:
|
||||
# print("Error in HeartBeat")
|
||||
# print(f"Exception type: {type(e).__name__}")
|
||||
# print(f"Exception message: {str(e)}")
|
||||
pass
|
||||
try:
|
||||
NewPacket = WSJTXClass.WSJTX_Packet(fileContent, 0)
|
||||
NewPacket.Decode()
|
||||
|
||||
if NewPacket.PacketType == 1:
|
||||
# print('New Packet is type 1')
|
||||
StatusPacket = WSJTXClass.WSJTX_Status(fileContent, NewPacket.index)
|
||||
StatusPacket.Decode()
|
||||
|
||||
# Check TX frequency and update transceiver
|
||||
new_freq = StatusPacket.TxDF
|
||||
new_mode = StatusPacket.TxMode.strip()
|
||||
|
||||
|
||||
if new_freq != self.tx_freq:
|
||||
# time.sleep(0.03)
|
||||
print('Changing frequency from {0} to {1}'.format(self.tx_freq, new_freq))
|
||||
try:
|
||||
self.change_freq(new_freq)
|
||||
except Exception as e:
|
||||
print(f"Exception occurred: {e}")
|
||||
print ('It is now ', self.tx_freq)
|
||||
|
||||
# print('new_mode', new_mode, ' self.mode is: ', self.mode)
|
||||
if new_mode != self.mode:
|
||||
print("Mode before: {0}".format(self.mode))
|
||||
self.set_mode(new_mode)
|
||||
print("New mode after: {0}".format(self.mode))
|
||||
|
||||
# Check if TX is enabled
|
||||
if StatusPacket.Transmitting == 1:
|
||||
# Check time, avoid transmitting out of the time slot
|
||||
current_time = datetime.datetime.now()
|
||||
utc_time = current_time.astimezone(datetime.timezone.utc)
|
||||
self.tx_now = self.check_time_window(utc_time)
|
||||
if self.tx_now:
|
||||
self.command_sock.sendto(b'p', (self.PICO_UDP_IP, self.COMMAND_UDP_PORT))
|
||||
message = StatusPacket.TxMessage
|
||||
message = message.replace('<', '')
|
||||
message = message.replace('>', '')
|
||||
print('Message is: ', message)
|
||||
self.new_msg(message.strip())
|
||||
print("Encoded message is: ", self.current_msg)
|
||||
# print("tx_now is: ", self.tx_now)
|
||||
if self.tx_now:
|
||||
print('Transmitting')
|
||||
self.transmit()
|
||||
print("Time: {0}:{1}:{2}".format(utc_time.hour, utc_time.minute, utc_time.second))
|
||||
else:
|
||||
pass
|
||||
# print('Not Transmitting')
|
||||
return BaseHardware.HeartBeat(self)
|
||||
|
||||
except Exception as e:
|
||||
# print("Error in HeartBeat")
|
||||
# print(f"Exception type: {type(e).__name__}")
|
||||
# print(f"Exception message: {str(e)}")
|
||||
# traceback.print_exc()
|
||||
return BaseHardware.HeartBeat(self)
|
||||
@@ -4,14 +4,8 @@ import os
|
||||
|
||||
# This file is used to build the Linux and Mac versions of Quisk. Windows builds are not included.
|
||||
|
||||
# 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.52'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
fp.write("from .quisk import main\n")
|
||||
fp = open('__init__.py') # Read in the version
|
||||
Version = fp.readline().strip()[15:]
|
||||
fp.close()
|
||||
|
||||
sources = ['quisk.c', 'sound.c', 'is_key_down.c', 'microphone.c', 'utility.c',
|
||||
|
||||
+21
-4
@@ -1,7 +1,24 @@
|
||||
MODULE_NAME := soapy
|
||||
OBJDIR := ../build/local
|
||||
|
||||
soapy2:
|
||||
python2 setup.py build_ext --force --inplace
|
||||
CFLAGS := $(shell python3 ../makefil.py --cflags)
|
||||
LDFLAGS := $(shell python3 ../makefil.py --ldflags)
|
||||
EXTENSION_SUFFIX := $(shell python3 ../makefil.py --suffix)
|
||||
TARGET := $(MODULE_NAME)$(EXTENSION_SUFFIX)
|
||||
|
||||
soapy3:
|
||||
# Default target: Link the shared library object
|
||||
$(TARGET): soapy.o $(OBJDIR)/import_quisk_api.o
|
||||
$(CC) -o $@ soapy.o $(LDFLAGS) -lSoapySDR $(OBJDIR)/import_quisk_api.o
|
||||
|
||||
# Compile the C source files into object files
|
||||
soapy.o: soapy.c ../quisk.h ../filter.h
|
||||
$(CC) -o $@ -c $< $(CFLAGS) -I..
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -f *.o
|
||||
-rm -f $(MODULE_NAME)*.so
|
||||
|
||||
.PHONY: soapy-old
|
||||
soapy-old:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
|
||||
|
||||
+8
-7
@@ -1,5 +1,6 @@
|
||||
#include <Python.h>
|
||||
#include <complex.h>
|
||||
#include <string.h>
|
||||
#include <SoapySDR/Device.h>
|
||||
#include <SoapySDR/Formats.h>
|
||||
#include <SoapySDR/Version.h>
|
||||
@@ -223,7 +224,7 @@ static PyObject * open_device(PyObject * self, PyObject * args)
|
||||
else {
|
||||
snprintf(buf128, 128, "SoapySDRDevice_make fail: %s", SoapySDRDevice_lastError());
|
||||
}
|
||||
return PyString_FromString(buf128);
|
||||
return PyUnicode_FromString(buf128);
|
||||
}
|
||||
|
||||
static void get_direc_len(const char * name, int * direction, int * length)
|
||||
@@ -254,7 +255,7 @@ static PyObject * get_device_list(PyObject * self, PyObject * args) // Called fr
|
||||
for (i = 0; i < length; i++) {
|
||||
dict = PyDict_New();
|
||||
for (j = 0; j < results[i].size; j++)
|
||||
PyDict_SetItemString(dict, results[i].keys[j], PyString_FromString(results[i].vals[j]));
|
||||
PyDict_SetItemString(dict, results[i].keys[j], PyUnicode_FromString(results[i].vals[j]));
|
||||
PyList_Append(devices, dict);
|
||||
Py_DECREF(dict);
|
||||
}
|
||||
@@ -329,7 +330,7 @@ static PyObject * set_parameter(PyObject * self, PyObject * args) // Called from
|
||||
}
|
||||
}
|
||||
if (msg200[0])
|
||||
return PyString_FromString(msg200);
|
||||
return PyUnicode_FromString(msg200);
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
@@ -377,7 +378,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
|
||||
pylist = PyList_New(0);
|
||||
names = SoapySDRDevice_listAntennas(sdev, direction, 0, &len_list);
|
||||
for (i = 0; i < len_list; i++) {
|
||||
pyobj = PyString_FromString(names[i]);
|
||||
pyobj = PyUnicode_FromString(names[i]);
|
||||
PyList_Append(pylist, pyobj);
|
||||
Py_DECREF(pyobj);
|
||||
}
|
||||
@@ -440,7 +441,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
|
||||
pylist = PyList_New(0);
|
||||
names = SoapySDRDevice_listGains(sdev, direction, 0, &len_list);
|
||||
for (i = 0; i < len_list; i++) {
|
||||
pyobj = PyString_FromString(names[i]);
|
||||
pyobj = PyUnicode_FromString(names[i]);
|
||||
PyList_Append(pylist, pyobj);
|
||||
Py_DECREF(pyobj);
|
||||
}
|
||||
@@ -450,7 +451,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
|
||||
else if ( ! strncmp(name, "soapy_listGainsValues", length)) {
|
||||
pylist = PyList_New(0);
|
||||
pylst2 = PyList_New(0); // First element is the total gain
|
||||
pyobj = PyString_FromString("total");
|
||||
pyobj = PyUnicode_FromString("total");
|
||||
PyList_Append(pylst2, pyobj);
|
||||
Py_DECREF(pyobj);
|
||||
range = SoapySDRDevice_getGainRange(sdev, direction, 0);
|
||||
@@ -460,7 +461,7 @@ static PyObject * get_parameter(PyObject * self, PyObject * args) // Called from
|
||||
names = SoapySDRDevice_listGains(sdev, direction, 0, &len_list);
|
||||
for (i = 0; i < len_list; i++) {
|
||||
pylst2 = PyList_New(0);
|
||||
pyobj = PyString_FromString(names[i]);
|
||||
pyobj = PyUnicode_FromString(names[i]);
|
||||
PyList_Append(pylst2, pyobj);
|
||||
Py_DECREF(pyobj);
|
||||
range = SoapySDRDevice_getGainElementRange(sdev, direction, 0, names[i]);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include <complex.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
@@ -986,6 +987,8 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
// Perhaps replace the samples with samples from a file
|
||||
if (quisk_record_state == FILE_PLAY_SAMPLES)
|
||||
quisk_play_samples(cSamples, nSamples);
|
||||
// Send radio IQ samples to TCI
|
||||
tci_send_iq(cSamples, nSamples);
|
||||
#if ! DEBUG_MIC
|
||||
nSamples = quisk_process_samples(cSamples, nSamples);
|
||||
#endif
|
||||
|
||||
+5
-4
@@ -2,6 +2,7 @@
|
||||
* This modue provides sound access for QUISK using the ALSA
|
||||
* library for Linux.
|
||||
*/
|
||||
#include <string.h>
|
||||
#ifdef QUISK_HAVE_ALSA
|
||||
#include <Python.h>
|
||||
#include <complex.h>
|
||||
@@ -454,7 +455,7 @@ static int device_list(PyObject * py, snd_pcm_stream_t stream, char * name)
|
||||
}
|
||||
snprintf(buf100, 100, "%s %s (hw:%d,%d)", card_text, pcm_text, card, dev);
|
||||
if (py) { // add to list of devices
|
||||
PyList_Append(py, PyString_FromString(buf100));
|
||||
PyList_Append(py, PyUnicode_FromString(buf100));
|
||||
}
|
||||
if (name) { // return the "hw:" name
|
||||
if (strstr(buf100, name)) {
|
||||
@@ -1129,10 +1130,10 @@ static void midi_in_devices(PyObject * pylist, int just_names)
|
||||
}
|
||||
else {
|
||||
pytup = PyTuple_New(2);
|
||||
PyList_Append(pylist, pytup);
|
||||
PyTuple_SET_ITEM(pytup, 0, PyUnicode_DecodeUTF8(friendly_name, strlen(friendly_name), "replace"));
|
||||
PyTuple_SetItem(pytup, 0, PyUnicode_DecodeUTF8(friendly_name, strlen(friendly_name), "replace"));
|
||||
snprintf(card_name, 32, "hw:%d,%d,%d", card, device, sub);
|
||||
PyTuple_SET_ITEM(pytup, 1, PyUnicode_DecodeUTF8(card_name, strlen(card_name), "replace"));
|
||||
PyTuple_SetItem(pytup, 1, PyUnicode_DecodeUTF8(card_name, strlen(card_name), "replace"));
|
||||
PyList_Append(pylist, pytup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-8
@@ -1,3 +1,5 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifdef QUISK_HAVE_DIRECTX
|
||||
|
||||
#include <Python.h>
|
||||
@@ -55,14 +57,7 @@ static int match_name(LPCTSTR lpszDesc, const char * name)
|
||||
Py_DECREF(py_unicode);
|
||||
return 0;
|
||||
}
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
if (PyUnicode_READY(py_unicode) == 0)
|
||||
length = PyUnicode_GET_LENGTH(py_unicode);
|
||||
else
|
||||
length = 0;
|
||||
#else
|
||||
length = PyUnicode_GET_SIZE(py_unicode);
|
||||
#endif
|
||||
length = PyUnicode_GetLength(py_unicode);
|
||||
if (length <= 0) {
|
||||
Py_DECREF(py_unicode);
|
||||
Py_DECREF(py_substring);
|
||||
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* This module provides sound access for QUISK using the portaudio library.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#ifdef QUISK_HAVE_PORTAUDIO
|
||||
|
||||
#include <Python.h>
|
||||
@@ -475,7 +477,7 @@ static int device_list(PyObject * py, int input)
|
||||
// found one
|
||||
if (py) {
|
||||
snprintf(buf100, 100, "%s", info->name);
|
||||
PyList_Append(py, PyString_FromString(buf100));
|
||||
PyList_Append(py, PyUnicode_FromString(buf100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -34,6 +34,8 @@
|
||||
* 2020 Apr by N2ADR Changes in printf's
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef QUISK_HAVE_PULSEAUDIO
|
||||
|
||||
#include <Python.h>
|
||||
@@ -1030,20 +1032,20 @@ static void source_sink(const char * name, const char * descr, pa_proplist * pro
|
||||
PyObject * pytup;
|
||||
|
||||
pytup = PyTuple_New(3);
|
||||
PyList_Append(pylist, pytup);
|
||||
PyTuple_SET_ITEM(pytup, 0, PyString_FromString(name));
|
||||
PyTuple_SET_ITEM(pytup, 1, PyString_FromString(descr));
|
||||
PyTuple_SetItem(pytup, 0, PyUnicode_FromString(name));
|
||||
PyTuple_SetItem(pytup, 1, PyUnicode_FromString(descr));
|
||||
value = pa_proplist_gets(props, "device.api");
|
||||
|
||||
if (value && ! strcmp(value, "alsa")) {
|
||||
snprintf(buf300, 300, "%s %s (hw:%s,%s)", pa_proplist_gets(props, "alsa.card_name"), pa_proplist_gets(props, "alsa.name"),
|
||||
pa_proplist_gets(props, "alsa.card"), pa_proplist_gets(props, "alsa.device"));
|
||||
|
||||
PyTuple_SET_ITEM(pytup, 2, PyString_FromString(buf300));
|
||||
PyTuple_SetItem(pytup, 2, PyUnicode_FromString(buf300));
|
||||
}
|
||||
else {
|
||||
PyTuple_SET_ITEM(pytup, 2, PyString_FromString(""));
|
||||
PyTuple_SetItem(pytup, 2, PyUnicode_FromString(""));
|
||||
}
|
||||
PyList_Append(pylist, pytup);
|
||||
}
|
||||
|
||||
|
||||
@@ -1074,7 +1076,7 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
|
||||
//printf("%u\n", idx);
|
||||
}
|
||||
|
||||
PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args)
|
||||
PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args) // Called from the GUI thread
|
||||
{ // Return a list of PulseAudio device names [pycapt, pyplay]
|
||||
PyObject * pylist, * pycapt, * pyplay;
|
||||
pa_mainloop *pa_names_ml;
|
||||
|
||||
+9
-6
@@ -7,6 +7,9 @@
|
||||
* Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
|
||||
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef QUISK_HAVE_WASAPI
|
||||
|
||||
#define UNICODE
|
||||
@@ -1125,10 +1128,10 @@ PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Call
|
||||
EXIT_ON_ERROR(hr)
|
||||
// data items are (name, id, is_raw)
|
||||
pytup = PyTuple_New(3);
|
||||
PyTuple_SetItem(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
|
||||
PyTuple_SetItem(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
|
||||
PyTuple_SetItem(pytup, 2, PyInt_FromLong(1));
|
||||
PyList_Append(pyplay, pytup);
|
||||
PyTuple_SET_ITEM(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
|
||||
PyTuple_SET_ITEM(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
|
||||
PyTuple_SET_ITEM(pytup, 2, PyInt_FromLong(1));
|
||||
CoTaskMemFree(pwszID);
|
||||
pwszID = NULL;
|
||||
PropVariantClear(&varName);
|
||||
@@ -1155,10 +1158,10 @@ PyObject * quisk_wasapi_sound_devices(PyObject * self, PyObject * args) // Call
|
||||
EXIT_ON_ERROR(hr)
|
||||
// data items are (name, id, is_raw)
|
||||
pytup = PyTuple_New(3);
|
||||
PyTuple_SetItem(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
|
||||
PyTuple_SetItem(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
|
||||
PyTuple_SetItem(pytup, 2, PyInt_FromLong(1));
|
||||
PyList_Append(pycapt, pytup);
|
||||
PyTuple_SET_ITEM(pytup, 0, PyUnicode_FromWideChar(varName.pwszVal, -1));
|
||||
PyTuple_SET_ITEM(pytup, 1, PyUnicode_FromWideChar(pwszID, -1));
|
||||
PyTuple_SET_ITEM(pytup, 2, PyInt_FromLong(1));
|
||||
CoTaskMemFree(pwszID);
|
||||
pwszID = NULL;
|
||||
PropVariantClear(&varName);
|
||||
|
||||
@@ -2,36 +2,37 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "ws.h"
|
||||
#include <pthread.h>
|
||||
#include <complex.h>
|
||||
#include <time.h>
|
||||
#include "quisk.h"
|
||||
#include "filter.h"
|
||||
|
||||
#define TCI_STREAM_DATA_BYTES 16384
|
||||
#define TCI_RX_BUF_SIZE 1024
|
||||
#define TCI_COMMAND_SIZE 32
|
||||
#define TCI_COMMAND_SIZE 64
|
||||
#define BAD_TCI_IF 999999999
|
||||
|
||||
static int verbose; // non-zero for verbose log messages
|
||||
static int tci_port;
|
||||
|
||||
// Implement the ExpertSDR2 Version 1.4 protocol:
|
||||
// The only sample rate is 48000. The Tx audio parameters are copied from the Rx audio stream.
|
||||
// The Tx audio parameters are copied from the Rx audio stream.
|
||||
// The only sample type is FLOAT32 type 3.
|
||||
// WSJT-X RX_AUDIO_STREAM must be two channels and Stream.length is the number of floats (not samples).
|
||||
// WSJT-X TX_AUDIO_STREAM always returns two channels and Stream.length is the number of floats (not samples).
|
||||
// WSJT-X TX_AUDIO_STREAM is twice as long, but the data is in the first half and garbage is in the second half.
|
||||
// WSJT-X TX_CHRONO Stream.length is the number of floats, and the returned TX_AUDIO_STREAM length is equal to it.
|
||||
// It is unclear whether Stream.length is the number of floats, or half that for two channels. And version 1.4 lacks Stream.channels.
|
||||
// The protocol name for TCI version 1.4 is "protocol:ESDR,1.4;" and the port is 40001.
|
||||
// The protocol name for TCI version 2.0 is "protocol:ExpertSDR3,2.0;" and the port is 50001.
|
||||
// The TCI modulations are: am, sam, dsb, lsb, usb, cw, nfm, wfm, spec, digl, digu, drm.
|
||||
|
||||
// These are TCI parameters:
|
||||
//static int64_t quisk_dds;
|
||||
//static int quisk_if;
|
||||
static long long quisk_dds;
|
||||
static long long client_dds=-1;
|
||||
static long long quisk_vfo;
|
||||
static long long client_vfo=-1;
|
||||
static int quisk_if;
|
||||
static int client_if=BAD_TCI_IF;
|
||||
static int quisk_trx;
|
||||
static int client_trx=-1;
|
||||
static int quisk_split_enable;
|
||||
@@ -79,6 +80,11 @@ struct ClientData { // data for each client
|
||||
int audio_stream_channels;
|
||||
int audio_stream_samples;
|
||||
int audio_stream_bytes_per_sample;
|
||||
struct quisk_cHB45Filter HalfBand1;
|
||||
struct quisk_cFilter filtDecim3;
|
||||
struct quisk_cFilter filtDecim48to24;
|
||||
int send_IQ_stream;
|
||||
int IQ_stream_samplerate;
|
||||
} ;
|
||||
|
||||
struct _Stream {
|
||||
@@ -132,11 +138,11 @@ struct _StreamTypes StreamTypes[NUM_STREAM_TYPES] = {
|
||||
{"lineout ", 0}
|
||||
} ;
|
||||
|
||||
static void PrintStream(const char * msg, size_t size)
|
||||
static void PrintStream(const char * msg, size_t size, int always)
|
||||
{
|
||||
struct _Stream * pt = (struct _Stream *)msg;
|
||||
|
||||
if (time(NULL) - StreamTypes[pt->type].when > 5) {
|
||||
if (always || time(NULL) - StreamTypes[pt->type].when > 5) {
|
||||
StreamTypes[pt->type].when = time(NULL);
|
||||
if (pt->type == TX_AUDIO_STREAM)
|
||||
QuiskPrintf("TCI Receiving %slength %5d:", StreamTypes[pt->type].name, (int)size);
|
||||
@@ -164,7 +170,7 @@ static int sendframe_txt_bcast(uint16_t port, const char * msg)
|
||||
static size_t sendframe_bin(ws_cli_conn_t client, const char * msg, size_t size)
|
||||
{
|
||||
if (verbose)
|
||||
PrintStream(msg, size);
|
||||
PrintStream(msg, size, 0);
|
||||
return ws_sendframe_bin(client, msg, size);
|
||||
}
|
||||
|
||||
@@ -177,8 +183,12 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
|
||||
if (verbose)
|
||||
QuiskPrintf("TCI Received text %s\n", ctx->msg_buf);
|
||||
for (i = 0; ctx->msg_buf[i]; i++)
|
||||
//QuiskPrintf("TCI Received text %s %20.6lf\n", ctx->msg_buf, QuiskTimeSec());
|
||||
for (i = 0; ctx->msg_buf[i]; i++) {
|
||||
char_buf[i] = ctx->msg_buf[i]; // make a copy of the command
|
||||
ctx->msg_buf[i] = tolower(ctx->msg_buf[i]);
|
||||
}
|
||||
char_buf[i] = 0;
|
||||
command = strtok_r(ctx->msg_buf, ":", &saveptr);
|
||||
if (command == NULL)
|
||||
return 0;
|
||||
@@ -193,10 +203,19 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
for (i = 0; i < tci_clients_count; i++) {
|
||||
if (tci_clients_list[i] == client) {
|
||||
ctx->send_Rx_audio_stream = 1;
|
||||
memset(&ctx->HalfBand1,
|
||||
0, sizeof(struct quisk_cHB45Filter));
|
||||
quisk_filt_cInit(&ctx->filtDecim3,
|
||||
quiskFilt144D3Coefs, sizeof(quiskFilt144D3Coefs)/sizeof(double));
|
||||
quisk_filt_cInit(&ctx->filtDecim48to24,
|
||||
quiskFilt48dec24Coefs, sizeof(quiskFilt48dec24Coefs)/sizeof(double));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "audio_stop") == 0) {
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
@@ -207,18 +226,23 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "audio_stream_sample_type") == 0) {
|
||||
if (strcmp(arg1, "float32") == 0) {
|
||||
ctx->audio_stream_sample_type = TCI_FLOAT32;
|
||||
ctx->audio_stream_bytes_per_sample = 4;
|
||||
sendframe_txt(client, char_buf);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (strcmp(command, "audio_samplerate") == 0) {
|
||||
if (atoi(arg1) != 48000)
|
||||
i = atoi(arg1);
|
||||
if (i != 48000 && i != 24000 && i != 12000 && i != 8000)
|
||||
return 0;
|
||||
ctx->audio_stream_samplerate = i;
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "audio_stream_channels") == 0) {
|
||||
@@ -228,23 +252,74 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
ctx->audio_stream_channels = 2;
|
||||
else
|
||||
return 0;
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "audio_stream_samples") == 0) {
|
||||
return 0; // do not send
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
if (strcmp(command, "dds") == 0) {
|
||||
if (arg2) {
|
||||
client_dds = atoll(arg2);
|
||||
// Echo comes from quisk_tci_set_params()
|
||||
}
|
||||
else {
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "dds:0,%lld;", quisk_dds);
|
||||
sendframe_txt(client, char_buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
if (strcmp(command, "iq_start") == 0)
|
||||
return 0; // do not send
|
||||
else if (strcmp(command, "iq_stop") == 0)
|
||||
return 0; // do not send
|
||||
else if (strcmp(command, "iq_samplerate") == 0)
|
||||
return 0; // do not send
|
||||
if (strcmp(command, "iq_start") == 0) {
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
for (i = 0; i < tci_clients_count; i++) {
|
||||
if (tci_clients_list[i] == client) {
|
||||
ctx->send_IQ_stream = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "iq_stop") == 0) {
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
for (i = 0; i < tci_clients_count; i++) {
|
||||
if (tci_clients_list[i] == client) {
|
||||
ctx->send_IQ_stream = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "iq_samplerate") == 0) {
|
||||
ctx->IQ_stream_samplerate = quisk_sound_state.sample_rate;
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "iq_samplerate:%d;", quisk_sound_state.sample_rate);
|
||||
sendframe_txt(client, char_buf);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "if") == 0) {
|
||||
if (arg3) {
|
||||
client_if = atoi(arg3);
|
||||
// Echo comes from quisk_tci_set_params() UNIMPLEMENTED
|
||||
}
|
||||
else {
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
|
||||
sendframe_txt(client, char_buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (strcmp(command, "modulation") == 0) {
|
||||
if (arg2) {
|
||||
strncpy(client_modulation, arg2, TCI_COMMAND_SIZE - 1);
|
||||
// Echo comes from quisk_tci_set_params()
|
||||
}
|
||||
else {
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
|
||||
@@ -260,6 +335,7 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
client_split_enable = 1;
|
||||
else
|
||||
client_split_enable = 0;
|
||||
// Echo comes from quisk_tci_set_params()
|
||||
}
|
||||
else {
|
||||
if (quisk_split_enable)
|
||||
@@ -291,6 +367,7 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
client_trx = 0;
|
||||
tci_tx_audio_client = 0;
|
||||
}
|
||||
// Echo comes from quisk_tci_set_params()
|
||||
}
|
||||
else {
|
||||
if (quisk_trx)
|
||||
@@ -308,6 +385,7 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
if (strcmp(command, "vfo") == 0) {
|
||||
if (arg3) {
|
||||
client_vfo = atoll(arg3);
|
||||
// Echo comes from quisk_tci_set_params()
|
||||
}
|
||||
else {
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", quisk_vfo);
|
||||
@@ -330,7 +408,9 @@ static void onopen(ws_cli_conn_t client)
|
||||
ctx = malloc(sizeof(struct ClientData));
|
||||
ctx->msg_length = 0;
|
||||
ctx->send_Rx_audio_stream = 0;
|
||||
ctx->send_IQ_stream = 0;
|
||||
ctx->audio_stream_samplerate = 48000;
|
||||
ctx->IQ_stream_samplerate = quisk_sound_state.sample_rate;
|
||||
ctx->audio_stream_sample_type = TCI_FLOAT32;
|
||||
ctx->audio_stream_channels = 2;
|
||||
ctx->audio_stream_samples = 0;
|
||||
@@ -348,7 +428,9 @@ static void onopen(ws_cli_conn_t client)
|
||||
|
||||
sendframe_txt(client, "protocol:ESDR,1.4;");
|
||||
//sendframe_txt(client, "vfo_limits:30000,30000000;"); Not known due to transverters
|
||||
//sendframe_txt(client, "if_limits:-48000,48000;");
|
||||
int if_limit = quisk_sound_state.sample_rate / 2;
|
||||
snprintf(command, TCI_COMMAND_SIZE, "if_limits:%d,%d;", - if_limit, if_limit);
|
||||
sendframe_txt(client, command);
|
||||
sendframe_txt(client, "trx_count:1;");
|
||||
sendframe_txt(client, "channel_count:1;");
|
||||
sendframe_txt(client, "device:QuiskSDR;");
|
||||
@@ -358,10 +440,10 @@ static void onopen(ws_cli_conn_t client)
|
||||
|
||||
sendframe_txt(client, "start;");
|
||||
sendframe_txt(client, "tx_enable:0,true;");
|
||||
//snprintf(command, TCI_COMMAND_SIZE, "dds:0,%d;", quisk_dds);
|
||||
//sendframe_txt(client, command);
|
||||
//snprintf(command, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
|
||||
//sendframe_txt(client, command);
|
||||
snprintf(command, TCI_COMMAND_SIZE, "dds:0,%lld;", quisk_dds);
|
||||
sendframe_txt(client, command);
|
||||
snprintf(command, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
|
||||
sendframe_txt(client, command);
|
||||
snprintf(command, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
|
||||
sendframe_txt(client, command);
|
||||
snprintf(command, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", quisk_vfo);
|
||||
@@ -471,7 +553,7 @@ static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t s
|
||||
break;
|
||||
case TCI_FLOAT32:
|
||||
// Version 1.4 does not return the number of channels. We assume two channels.
|
||||
// pt->length is the number of floats in WSJT-X.
|
||||
// pt->length is the number of floats
|
||||
while (vpt < vpt_end && count < pt->length) {
|
||||
re = *(float *)vpt;
|
||||
vpt += sizeof(float);
|
||||
@@ -499,7 +581,7 @@ static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t s
|
||||
}
|
||||
}
|
||||
if (verbose)
|
||||
PrintStream((const char *)msg, size);
|
||||
PrintStream((const char *)msg, size, 0);
|
||||
|
||||
}
|
||||
|
||||
@@ -529,11 +611,37 @@ static void tci_startup(void)
|
||||
return;
|
||||
}
|
||||
|
||||
static int decimate_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx)
|
||||
{
|
||||
switch(ctx->audio_stream_samplerate) { // Starting audio rate is 48000
|
||||
case 48000:
|
||||
break;
|
||||
case 24000:
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &ctx->filtDecim48to24, 2);
|
||||
break;
|
||||
case 12000:
|
||||
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &ctx->HalfBand1);
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &ctx->filtDecim48to24, 2);
|
||||
break;
|
||||
case 8000:
|
||||
nSamples = quisk_cDecim2HB45(cSamples, nSamples, &ctx->HalfBand1);
|
||||
nSamples = quisk_cDecimate(cSamples, nSamples, &ctx->filtDecim3, 3);
|
||||
break;
|
||||
default:
|
||||
nSamples = 0;
|
||||
break;
|
||||
}
|
||||
return nSamples;
|
||||
}
|
||||
|
||||
void tci_send_audio(complex double * cSamples, int nSamples) // called from the sound thread
|
||||
{
|
||||
int i, n;
|
||||
struct _Stream stream;
|
||||
struct ClientData * ctx;
|
||||
static complex double * cCopy = NULL;
|
||||
static int cCopy_size = 0;
|
||||
complex double * cAudio;
|
||||
|
||||
if (tci_clients_count <= 0)
|
||||
return;
|
||||
@@ -555,15 +663,29 @@ void tci_send_audio(complex double * cSamples, int nSamples) // called from the
|
||||
int bytes_per_sample = ctx->audio_stream_bytes_per_sample;
|
||||
void * vpt = &stream.data;
|
||||
void * vpt_end = vpt + TCI_STREAM_DATA_BYTES;
|
||||
if (ctx->audio_stream_samplerate != 48000) { // decimation must not change cSamples
|
||||
if (cCopy_size < nSamples) {
|
||||
if (cCopy)
|
||||
free(cCopy);
|
||||
cCopy_size = nSamples * 2;
|
||||
cCopy = (complex double *)malloc(cCopy_size * sizeof(complex double));
|
||||
}
|
||||
memcpy(cCopy, cSamples, nSamples * sizeof(complex double));
|
||||
nSamples = decimate_audio_stream(cCopy, nSamples, ctx);
|
||||
cAudio = cCopy;
|
||||
}
|
||||
else {
|
||||
cAudio = cSamples;
|
||||
}
|
||||
switch (ctx->audio_stream_sample_type) {
|
||||
case TCI_FLOAT32:
|
||||
stream.length = 0; // make a frame
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
*(float *)vpt = (float)(creal(cSamples[i]) * (1.0 / 2147483648.0 / 2));
|
||||
*(float *)vpt = (float)(creal(cAudio[i]) * (1.0 / 2147483648.0 / 2));
|
||||
vpt += bytes_per_sample;
|
||||
stream.length++;
|
||||
if (two_channels) {
|
||||
*(float *)vpt = (float)(cimag(cSamples[i]) * (1.0 / 2147483648.0 / 2));
|
||||
*(float *)vpt = (float)(cimag(cAudio[i]) * (1.0 / 2147483648.0 / 2));
|
||||
vpt += bytes_per_sample;
|
||||
stream.length++;
|
||||
}
|
||||
@@ -571,6 +693,7 @@ void tci_send_audio(complex double * cSamples, int nSamples) // called from the
|
||||
sendframe_bin(tci_clients_list[n], (const char *)&stream,
|
||||
(16 * sizeof(uint32_t) + stream.length * bytes_per_sample));
|
||||
stream.length = 0;
|
||||
vpt = &stream.data;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -579,6 +702,58 @@ void tci_send_audio(complex double * cSamples, int nSamples) // called from the
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
}
|
||||
|
||||
void tci_send_iq(complex double * cSamples, int nSamples) // called from the sound thread
|
||||
{
|
||||
int i, n;
|
||||
struct _Stream stream;
|
||||
struct ClientData * ctx;
|
||||
|
||||
if (tci_clients_count <= 0)
|
||||
return;
|
||||
|
||||
if (nSamples <= 0)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
for (n = 0; n < tci_clients_count; n++) {
|
||||
ctx = ws_get_connection_context(tci_clients_list[n]);
|
||||
if (ctx->send_IQ_stream == 0)
|
||||
continue;
|
||||
memset(&stream, 0, 16 * sizeof(uint32_t));
|
||||
stream.sample_rate = ctx->IQ_stream_samplerate;
|
||||
stream.format = ctx->audio_stream_sample_type;
|
||||
stream.type = IQ_STREAM;
|
||||
stream.channels = 2;
|
||||
int bytes_per_sample = ctx->audio_stream_bytes_per_sample;
|
||||
void * vpt = &stream.data;
|
||||
void * vpt_end = vpt + TCI_STREAM_DATA_BYTES;
|
||||
int original_length = nSamples;
|
||||
switch (ctx->audio_stream_sample_type) {
|
||||
case TCI_FLOAT32:
|
||||
stream.length = 0; // make a frame
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
*(float *)vpt = (float)(creal(cSamples[i]) * (1.0 / 2147483648.0 / 2));
|
||||
vpt += bytes_per_sample;
|
||||
stream.length++;
|
||||
*(float *)vpt = (float)(cimag(cSamples[i]) * (1.0 / 2147483648.0 / 2));
|
||||
vpt += bytes_per_sample;
|
||||
stream.length++;
|
||||
if (vpt >= vpt_end || i == nSamples - 1) {
|
||||
sendframe_bin(tci_clients_list[n], (const char *)&stream,
|
||||
(16 * sizeof(uint32_t) + stream.length * bytes_per_sample));
|
||||
original_length -= stream.length / 2;
|
||||
stream.length = 0;
|
||||
vpt = &stream.data;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (verbose && original_length != 0)
|
||||
QuiskPrintf("Error in tci_send_iq: Samples remain %d\n", original_length);
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
}
|
||||
|
||||
int tci_get_mic(complex double * cSamples, int mic_count) // called from the sound thread
|
||||
{
|
||||
if (tci_tx_audio_client) {
|
||||
@@ -608,16 +783,17 @@ int tci_get_mic(complex double * cSamples, int mic_count) // called from the sou
|
||||
PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * keywds) // called from the GUI thread
|
||||
{ /* Call with keyword arguments ONLY.
|
||||
Sent from Quisk when parameters change. Broadcast the change to clients.*/
|
||||
static char * kwlist[] = {"start", "verbose", "tci_dds", "tci_if", "tci_vfo", "tci_trx", "tci_split_enable",
|
||||
static char * kwlist[] = {"start", "close", "verbose", "tci_dds", "tci_if", "tci_vfo", "tci_trx", "tci_split_enable",
|
||||
"tci_modulation", NULL} ;
|
||||
long long new_vfo=-1;
|
||||
long long new_dds=-1;
|
||||
int start=-1, new_if=-1, new_trx=-1, new_split_enable=-1;
|
||||
int start=-1, close=-1, new_trx=-1, new_split_enable=-1;
|
||||
int new_if = BAD_TCI_IF;
|
||||
char * mode=NULL;
|
||||
char char_buf[TCI_COMMAND_SIZE];
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiLiLiis", kwlist,
|
||||
&start, &verbose, &new_dds, &new_if, &new_vfo, &new_trx, &new_split_enable,
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiLiLiis", kwlist,
|
||||
&start, &close, &verbose, &new_dds, &new_if, &new_vfo, &new_trx, &new_split_enable,
|
||||
&mode))
|
||||
return NULL;
|
||||
if (start == 1) {
|
||||
@@ -628,6 +804,19 @@ PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * key
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
if (close != -1) {
|
||||
sendframe_txt_bcast(tci_port, "stop;");
|
||||
}
|
||||
if (new_dds != -1) {
|
||||
quisk_dds = new_dds;
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "dds:0,%lld;", quisk_dds);
|
||||
sendframe_txt_bcast(tci_port, char_buf);
|
||||
}
|
||||
if (new_if != BAD_TCI_IF) {
|
||||
quisk_if = new_if;
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
|
||||
sendframe_txt_bcast(tci_port, char_buf);
|
||||
}
|
||||
if (new_vfo != -1) {
|
||||
quisk_vfo = new_vfo;
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", quisk_vfo);
|
||||
@@ -683,6 +872,10 @@ PyObject * quisk_tci_get_params(PyObject * self, PyObject * args) // called from
|
||||
|
||||
if (!PyArg_ParseTuple (args, "s", &name))
|
||||
return NULL;
|
||||
|
||||
if (strcmp(name, "tci_clients_count") == 0)
|
||||
return PyLong_FromLong(tci_clients_count);
|
||||
|
||||
if (tci_started == 0) {
|
||||
}
|
||||
else if (strcmp(name, "tci_vfo") == 0 && client_vfo >= 0) {
|
||||
@@ -690,6 +883,16 @@ PyObject * quisk_tci_get_params(PyObject * self, PyObject * args) // called from
|
||||
client_vfo = -1;
|
||||
return PyLong_FromLongLong(ll);
|
||||
}
|
||||
else if (strcmp(name, "tci_dds") == 0 && client_dds >= 0) {
|
||||
ll = client_dds;
|
||||
client_dds = -1;
|
||||
return PyLong_FromLongLong(ll);
|
||||
}
|
||||
else if (strcmp(name, "tci_if") == 0 && client_if != BAD_TCI_IF) {
|
||||
ii = client_if;
|
||||
client_if = BAD_TCI_IF;
|
||||
return PyLong_FromLong(ii);
|
||||
}
|
||||
else if (strcmp(name, "tci_split_enable") == 0 && client_split_enable >= 0) {
|
||||
ii = client_split_enable;
|
||||
client_split_enable = -1;
|
||||
@@ -718,7 +921,7 @@ PyObject * quisk_tci_get_params(PyObject * self, PyObject * args) // called from
|
||||
strcpy(char_buf, "FM");
|
||||
client_modulation[0] = 0;
|
||||
if (char_buf[0])
|
||||
return PyString_FromString(char_buf);
|
||||
return PyUnicode_FromString(char_buf);
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <complex.h>
|
||||
#include <string.h>
|
||||
#include "quisk.h"
|
||||
|
||||
// Access to config file attributes.
|
||||
@@ -71,40 +72,27 @@ double QuiskGetConfigDouble(const char * name, double deflt)
|
||||
|
||||
char * QuiskGetConfigString(const char * name, char * deflt)
|
||||
{ // Return the UTF-8 configuration string. Return deflt for failure.
|
||||
char * res;
|
||||
PyObject * attr;
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
static char retbuf[QUISK_SC_SIZE];
|
||||
#endif
|
||||
Py_ssize_t size;
|
||||
|
||||
if (!quisk_pyConfig || PyErr_Occurred())
|
||||
return deflt;
|
||||
attr = PyObject_GetAttrString(quisk_pyConfig, name);
|
||||
if (attr) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
res = (char *)PyUnicode_AsUTF8(attr);
|
||||
#else
|
||||
if (PyUnicode_Check(attr)) {
|
||||
PyObject * pystr = PyUnicode_AsUTF8String(attr);
|
||||
strMcpy(retbuf, PyString_AsString(pystr), QUISK_SC_SIZE);
|
||||
retbuf[QUISK_SC_SIZE - 1] = 0;
|
||||
res = retbuf;
|
||||
Py_DECREF(pystr);
|
||||
}
|
||||
else {
|
||||
res = PyString_AsString(attr);
|
||||
}
|
||||
#endif
|
||||
Py_DECREF(attr);
|
||||
if (res)
|
||||
return res; // success
|
||||
else
|
||||
PyErr_Clear();
|
||||
}
|
||||
else {
|
||||
PyErr_Clear();
|
||||
}
|
||||
return deflt; // failure
|
||||
PyObject * attr = PyObject_GetAttrString(quisk_pyConfig, name);
|
||||
if ( ! attr) {
|
||||
PyErr_Clear();
|
||||
return deflt; // failure
|
||||
}
|
||||
if ( ! PyUnicode_Check(attr)) {
|
||||
Py_DECREF(attr);
|
||||
return deflt; // failure
|
||||
}
|
||||
const char * utf8_str = PyUnicode_AsUTF8AndSize(attr, &size);
|
||||
if ( ! utf8_str) {
|
||||
Py_DECREF(attr);
|
||||
PyErr_Clear();
|
||||
return deflt; // failure
|
||||
}
|
||||
Py_DECREF(attr);
|
||||
return (char *)utf8_str;
|
||||
}
|
||||
|
||||
double QuiskTimeSec(void)
|
||||
|
||||
Reference in New Issue
Block a user