Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c97ffa155e | ||
|
|
745882a506 | ||
|
|
674bbad1ab | ||
|
|
67c6fbcad9 |
@@ -1,3 +1,18 @@
|
|||||||
|
Quisk Version 4.2.55 October 2026
|
||||||
|
==================================
|
||||||
|
Karl Heinz, K5KHK, provided a patche to fix the AC2YD remote control head. He provided another patch
|
||||||
|
to fix Hamlib control when using WSJT-X improved. He added a mic jitter buffer to the remote control
|
||||||
|
head so it works over WiFi. Thanks Karl!
|
||||||
|
|
||||||
|
Quisk Version 4.2.54 August 2026
|
||||||
|
=================================
|
||||||
|
Quisk can now accept audio samples for Tx from TCI at rates 48000, 24000, 12000 and 8000. Please test.
|
||||||
|
|
||||||
|
Quisk now includes program icons in the directory "quisk/icons". The Windows icon is quisk/icons/quisk.ico.
|
||||||
|
On Windows, right-click the desktop icon and change the icon to <path-to-quisk>/quisk/icons/quisk.ico.
|
||||||
|
The Linux icon is quisk/icons/quisk.svg. On Linux, change the quisk.desktop file to
|
||||||
|
Icon=<path-to-quisk>/quisk/icons/quisk.svg.
|
||||||
|
|
||||||
Quisk Version 4.2.53 June 2026
|
Quisk Version 4.2.53 June 2026
|
||||||
===============================
|
===============================
|
||||||
I added TCI support for Software Defined Connectors CW skimmer. Please test.
|
I added TCI support for Software Defined Connectors CW skimmer. Please test.
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Quisk Software Defined Radio
|
||||||
|
|
||||||
|
Quisk is a software defined radio transceiver that runs on a PC or on a single board computer such as a Raspberry Pi.
|
||||||
|
It is written in Python and C.
|
||||||
|
It works with various kinds of radio hardware such as SoftRock, Hermes Lite 2,
|
||||||
|
the SDR-IQ by RfSpace, and with network UDP samples.
|
||||||
|
Quisk is easily extensible by writing your own hardware file in Python.
|
||||||
|
|
||||||
|
I wrote Quisk back in 2008, and I have been maintaining it ever since.
|
||||||
|
For questions and support, please visit the group https://groups.io/g/n2adr-sdr.
|
||||||
|
|
||||||
|
For more information see https://james.ahlstrom.name/quisk.
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#Quisk version 4.2.53
|
#Quisk version 4.2.55
|
||||||
from .quisk import main
|
from .quisk import main
|
||||||
|
|||||||
@@ -53,6 +53,41 @@ static int control_head_data_width; // app.data_width of control head for gr
|
|||||||
static int packets_sent;
|
static int packets_sent;
|
||||||
static int packets_recd;
|
static int packets_recd;
|
||||||
|
|
||||||
|
///// The jitter buffer is thanks to Karl Heinz - K5KHK
|
||||||
|
// Jitter buffer for the microphone samples received from the control head. The network can hold
|
||||||
|
// packets for 100 msec or more (Wi-Fi power saving and scanning) and then deliver them in a burst,
|
||||||
|
// but the Hermes Tx buffer only has 50 msec of slack. Without a buffer here, each late burst is an
|
||||||
|
// underflow (a gap on the air) followed by an overflow (discarded samples). So buffer mic_jb_target
|
||||||
|
// frames before playing, and then release them at a steady MIC_JB_RATE.
|
||||||
|
// The buffer adds its delay to the transmit audio, so it is off unless the configuration item
|
||||||
|
// remote_radio_mic_jitter_ms is set. When it is off, samples are passed on as they arrive.
|
||||||
|
#define MIC_JB_RATE 8000 // frames per second sent by the control head
|
||||||
|
#define MIC_JB_SIZE 8000 // ring buffer size in stereo frames
|
||||||
|
#define MIC_JB_MAX_MS 500 // largest configurable delay; MIC_JB_SIZE must hold this plus MIC_JB_EXTRA
|
||||||
|
#define MIC_JB_EXTRA 2000 // trim back to mic_jb_target above mic_jb_target + this (clock drift): 250 msec
|
||||||
|
#define MIC_JB_IDLE 0.5 // seconds without packets that end a transmission
|
||||||
|
|
||||||
|
static int16_t mic_jb[MIC_JB_SIZE * 2];
|
||||||
|
static int mic_jb_target; // frames to buffer before playing, from remote_radio_mic_jitter_ms; zero for off
|
||||||
|
static int mic_jb_read, mic_jb_count, mic_jb_playing, mic_jb_starved;
|
||||||
|
static int mic_jb_underruns, mic_jb_overruns;
|
||||||
|
static double mic_jb_time, mic_jb_frac, mic_jb_last_packet;
|
||||||
|
|
||||||
|
static void mic_jb_reset(void)
|
||||||
|
{
|
||||||
|
int msec;
|
||||||
|
|
||||||
|
msec = QuiskGetConfigInt("remote_radio_mic_jitter_ms", 0);
|
||||||
|
if (msec < 0)
|
||||||
|
msec = 0;
|
||||||
|
else if (msec > MIC_JB_MAX_MS)
|
||||||
|
msec = MIC_JB_MAX_MS;
|
||||||
|
mic_jb_target = msec * MIC_JB_RATE / 1000;
|
||||||
|
mic_jb_read = mic_jb_count = mic_jb_playing = mic_jb_starved = 0;
|
||||||
|
mic_jb_underruns = mic_jb_overruns = 0;
|
||||||
|
mic_jb_frac = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Receive stereo 16-bit pcm radio speaker sound on the control head via UDP
|
// Receive stereo 16-bit pcm radio speaker sound on the control head via UDP
|
||||||
int read_remote_radio_sound_socket(complex double * cSamples)
|
int read_remote_radio_sound_socket(complex double * cSamples)
|
||||||
{
|
{
|
||||||
@@ -117,9 +152,9 @@ int read_remote_radio_sound_socket(complex double * cSamples)
|
|||||||
// Receive stereo 16-bit pcm microphone samples at the remote radio via UDP
|
// Receive stereo 16-bit pcm microphone samples at the remote radio via UDP
|
||||||
int read_remote_mic_sound_socket(complex double * cSamples)
|
int read_remote_mic_sound_socket(complex double * cSamples)
|
||||||
{
|
{
|
||||||
int i, bytes, nInt16, nSamples;
|
int i, j, bytes, nInt16, nSamples, nFrames;
|
||||||
int16_t buf[UDP_SEND_INT16];
|
int16_t buf[UDP_SEND_INT16];
|
||||||
double samp_r, samp_l;
|
double now, due;
|
||||||
struct timeval tm_wait;
|
struct timeval tm_wait;
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
static struct quisk_cHB45Filter HalfBand;
|
static struct quisk_cHB45Filter HalfBand;
|
||||||
@@ -134,8 +169,8 @@ int read_remote_mic_sound_socket(complex double * cSamples)
|
|||||||
quisk_filt_cInit(&cFiltInterp3, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
|
quisk_filt_cInit(&cFiltInterp3, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
|
||||||
}
|
}
|
||||||
|
|
||||||
// read all available packets, one per loop
|
// read all available packets into the jitter buffer, one per loop
|
||||||
nSamples = 0;
|
now = QuiskTimeSec();
|
||||||
while (1) {
|
while (1) {
|
||||||
tm_wait.tv_sec = 0;
|
tm_wait.tv_sec = 0;
|
||||||
tm_wait.tv_usec = 0;
|
tm_wait.tv_usec = 0;
|
||||||
@@ -152,15 +187,71 @@ int read_remote_mic_sound_socket(complex double * cSamples)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (bytes > 0) {
|
if (bytes > 0) {
|
||||||
|
if (mic_jb_starved) { // the buffer ran dry; count it unless the transmission had ended
|
||||||
|
mic_jb_starved = 0;
|
||||||
|
if (now - mic_jb_last_packet < MIC_JB_IDLE)
|
||||||
|
mic_jb_underruns++;
|
||||||
|
}
|
||||||
|
mic_jb_last_packet = now;
|
||||||
nInt16 = bytes / 2;
|
nInt16 = bytes / 2;
|
||||||
for (i = 0; i < nInt16; i += 2) {
|
for (i = 0; i + 1 < nInt16; i += 2) {
|
||||||
samp_r = buf[i];
|
if (mic_jb_count >= MIC_JB_SIZE) { // full: drop the oldest frame
|
||||||
samp_l = buf[i + 1];
|
mic_jb_read = (mic_jb_read + 1) % MIC_JB_SIZE;
|
||||||
cSamples[nSamples++] = (samp_r + I * samp_l) / CLIP16 * CLIP32;
|
mic_jb_count--;
|
||||||
|
}
|
||||||
|
j = (mic_jb_read + mic_jb_count) % MIC_JB_SIZE * 2;
|
||||||
|
mic_jb[j] = buf[i];
|
||||||
|
mic_jb[j + 1] = buf[i + 1];
|
||||||
|
mic_jb_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // while(1)
|
} // while(1)
|
||||||
|
|
||||||
|
// release the frames that are due since the last call
|
||||||
|
nSamples = 0;
|
||||||
|
if (mic_jb_target == 0) { // the jitter buffer is off: pass on everything received
|
||||||
|
nFrames = mic_jb_count;
|
||||||
|
}
|
||||||
|
else if ( ! mic_jb_playing) {
|
||||||
|
nFrames = 0;
|
||||||
|
if (mic_jb_count >= mic_jb_target) {
|
||||||
|
mic_jb_playing = 1;
|
||||||
|
mic_jb_time = now;
|
||||||
|
mic_jb_frac = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
due = (now - mic_jb_time) * MIC_JB_RATE + mic_jb_frac;
|
||||||
|
if (due < 0) // the clock was set back
|
||||||
|
due = 0;
|
||||||
|
else if (due > MIC_JB_RATE / 2) // limit to 0.5 sec, well within SAMP_BUFFER_SIZE after interpolation
|
||||||
|
due = MIC_JB_RATE / 2;
|
||||||
|
mic_jb_time = now;
|
||||||
|
nFrames = (int)due;
|
||||||
|
mic_jb_frac = due - nFrames;
|
||||||
|
if (nFrames > mic_jb_count) { // ran dry: play what is left, then buffer up again
|
||||||
|
nFrames = mic_jb_count;
|
||||||
|
mic_jb_playing = 0;
|
||||||
|
mic_jb_starved = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < nFrames; i++) {
|
||||||
|
j = mic_jb_read * 2;
|
||||||
|
cSamples[nSamples++] = ((double)mic_jb[j] + I * (double)mic_jb[j + 1]) / CLIP16 * CLIP32;
|
||||||
|
mic_jb_read = (mic_jb_read + 1) % MIC_JB_SIZE;
|
||||||
|
}
|
||||||
|
mic_jb_count -= nFrames;
|
||||||
|
if (mic_jb_target && mic_jb_count > mic_jb_target + MIC_JB_EXTRA) { // the control head's clock is faster than ours
|
||||||
|
i = mic_jb_count - mic_jb_target;
|
||||||
|
mic_jb_read = (mic_jb_read + i) % MIC_JB_SIZE;
|
||||||
|
mic_jb_count -= i;
|
||||||
|
mic_jb_overruns++;
|
||||||
|
}
|
||||||
|
if ((mic_jb_underruns || mic_jb_overruns) && now - mic_jb_last_packet > MIC_JB_IDLE) {
|
||||||
|
QuiskPrintf("Remote mic jitter buffer: %d underruns, %d overruns\n", mic_jb_underruns, mic_jb_overruns);
|
||||||
|
mic_jb_underruns = mic_jb_overruns = 0;
|
||||||
|
}
|
||||||
|
|
||||||
nSamples = quisk_cInterpolate(cSamples, nSamples, &cFiltInterp3, 3);
|
nSamples = quisk_cInterpolate(cSamples, nSamples, &cFiltInterp3, 3);
|
||||||
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &HalfBand);
|
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &HalfBand);
|
||||||
|
|
||||||
@@ -716,6 +807,7 @@ PyObject * quisk_start_remote_radio_remote_sound(PyObject * self, PyObject * arg
|
|||||||
|
|
||||||
packets_sent = 0;
|
packets_sent = 0;
|
||||||
packets_recd = 0;
|
packets_recd = 0;
|
||||||
|
mic_jb_reset();
|
||||||
|
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
@@ -742,6 +834,7 @@ PyObject * quisk_stop_remote_radio_remote_sound(PyObject * self, PyObject * args
|
|||||||
control_head_graph_socket_started = 0;
|
control_head_graph_socket_started = 0;
|
||||||
remote_radio_graph_socket_started = 0;
|
remote_radio_graph_socket_started = 0;
|
||||||
control_head_data_width = 0;
|
control_head_data_width = 0;
|
||||||
|
mic_jb_reset();
|
||||||
|
|
||||||
QuiskPrintf("total packets sent = %i, recd = %i\n", packets_sent, packets_recd);
|
QuiskPrintf("total packets sent = %i, recd = %i\n", packets_sent, packets_recd);
|
||||||
|
|
||||||
|
|||||||
@@ -190,10 +190,20 @@ class Remot: # Remote comtrol base class
|
|||||||
return
|
return
|
||||||
try: # Read any data from the socket
|
try: # Read any data from the socket
|
||||||
text = self.remote_ctl_connection.recv(1024)
|
text = self.remote_ctl_connection.recv(1024)
|
||||||
except:
|
# Thanks to Karl Heinz - K5KHK
|
||||||
#traceback.print_exc()
|
except (BlockingIOError, InterruptedError, socket.timeout): # No data available now
|
||||||
|
return
|
||||||
|
except OSError: # For example ConnectionResetError: the Control Head is gone
|
||||||
|
print('Closing Remote Control connection: recv() failed')
|
||||||
|
self.RemoteCtlClose(False)
|
||||||
return
|
return
|
||||||
else: # We got some characters
|
else: # We got some characters
|
||||||
|
if not text: # recv() returns nothing only when the Control Head has closed the connection
|
||||||
|
# Close now rather than waiting for the heartbeat timeout: a Control Head that
|
||||||
|
# crashed or lost its network may have left the transmitter keyed.
|
||||||
|
print('Closing Remote Control connection: closed by Control Head')
|
||||||
|
self.RemoteCtlClose(False)
|
||||||
|
return
|
||||||
if not isinstance(text, str):
|
if not isinstance(text, str):
|
||||||
text = text.decode('utf-8')
|
text = text.decode('utf-8')
|
||||||
self.received += text
|
self.received += text
|
||||||
@@ -375,13 +385,22 @@ class Remot: # Remote comtrol base class
|
|||||||
self.cw_next_keydown = None
|
self.cw_next_keydown = None
|
||||||
|
|
||||||
def StopTransmit(self):
|
def StopTransmit(self):
|
||||||
# TODO: Add code for modes other than CW
|
# PTT code added thanks to Karl Heinz - K5KHK
|
||||||
|
# Called whenever the remote session ends (RemoteCtlClose). Release everything the
|
||||||
|
# Control Head may have keyed: the CW key and, for voice and digital modes, PTT.
|
||||||
|
# Without the PTT release, a Control Head that disappears while transmitting (a crash,
|
||||||
|
# a lost network, a dead battery) leaves the transmitter keyed until someone unkeys it
|
||||||
|
# at the radio.
|
||||||
while len(self.cw_event_queue):
|
while len(self.cw_event_queue):
|
||||||
self.cw_event_queue.popleft()
|
self.cw_event_queue.popleft()
|
||||||
self.cw_next_event_ts = None
|
self.cw_next_event_ts = None
|
||||||
self.cw_next_keydown = None
|
self.cw_next_keydown = None
|
||||||
self.cw_key_down = 0
|
self.cw_key_down = 0
|
||||||
QS.set_remote_cwkey(0)
|
QS.set_remote_cwkey(0)
|
||||||
|
ptt = getattr(self.app, 'pttButton', None)
|
||||||
|
if ptt and ptt.GetValue():
|
||||||
|
print('Remote Control connection closed while transmitting: releasing PTT')
|
||||||
|
ptt.SetValue(0, True) # Same as releasing PTT at the radio; runs OnButtonPTT
|
||||||
|
|
||||||
def ProcessRadioBtn(self, command, cmd_text):
|
def ProcessRadioBtn(self, command, cmd_text):
|
||||||
# Large and Small format screens send different button events for radio buttons.
|
# Large and Small format screens send different button events for radio buttons.
|
||||||
|
|||||||
@@ -2302,7 +2302,8 @@ class ControlMixin:
|
|||||||
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
|
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
|
||||||
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
|
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
|
||||||
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec',
|
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec',
|
||||||
'fixed_tune_offset', 'calibrate_temp_20', 'calibrate_temp_40', 'calibrate_current_0', 'calibrate_current_1'):
|
'fixed_tune_offset', 'calibrate_temp_20', 'calibrate_temp_40', 'calibrate_current_0', 'calibrate_current_1',
|
||||||
|
'remote_radio_mic_jitter_ms'):
|
||||||
setattr(conf, name, x)
|
setattr(conf, name, x)
|
||||||
application.ImmediateChange(name)
|
application.ImmediateChange(name)
|
||||||
elif name[0:4] in ('lin_', 'win_'):
|
elif name[0:4] in ('lin_', 'win_'):
|
||||||
|
|||||||
@@ -255,6 +255,8 @@ and then name the shortcut, perhaps "Quisk".
|
|||||||
If you check the properties you will see that Windows expanded the location and start directory to the full path.
|
If you check the properties you will see that Windows expanded the location and start directory to the full path.
|
||||||
If you are curious, Quisk and its Python source files are installed in "<b>your_install_directory</b>\Lib\site-packages\quisk".
|
If you are curious, Quisk and its Python source files are installed in "<b>your_install_directory</b>\Lib\site-packages\quisk".
|
||||||
If you change to that directory you can run Quisk with "pythonw quisk.py".
|
If you change to that directory you can run Quisk with "pythonw quisk.py".
|
||||||
|
Quisk has its icon file in quisk\icons. Right-click your desktop shortcut and change the Icon to
|
||||||
|
"<b>your_install_directory</b>\Lib\site-packages\quisk\icons\quisk.ico".
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<p>
|
<p>
|
||||||
@@ -389,6 +391,7 @@ make
|
|||||||
<p>
|
<p>
|
||||||
You run Quisk by running the file quisk.py with python3. For example, "python3 /home/jim/quisk/quisk.py".
|
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.
|
You can create a desktop icon to do that, but the method depends on your version of Linux.
|
||||||
|
The quisk icon is the file quisk/icons/quisk.svg.
|
||||||
<b>Never run Quisk with root privelege by using sudo. Fix the permission problem instead.</b>
|
<b>Never run Quisk with root privelege by using sudo. Fix the permission problem instead.</b>
|
||||||
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
|
To get started you must tell Quisk what kind of radio hardware you have. Press the Config button and select Radios.
|
||||||
Add a radio from the list of hardware radios available. Or add the best match.
|
Add a radio from the list of hardware radios available. Or add the best match.
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ double quiskFilt111D4Coefs[196] = { 0.000022860383861326, 0.000070265173417164,
|
|||||||
0.000878636067621920, 0.000694724116305488, 0.000487289901409396, 0.000301317969124407, 0.000160689233204503,
|
0.000878636067621920, 0.000694724116305488, 0.000487289901409396, 0.000301317969124407, 0.000160689233204503,
|
||||||
0.000070265173417164, 0.000022860383861326} ;
|
0.000070265173417164, 0.000022860383861326} ;
|
||||||
|
|
||||||
// Sample 53333 Hz, pass 20000, stop 24000, ripple 0.1 dB, atten 100 dB. For SDR-IQ. Stop 0.45000.
|
// Sample 53333 Hz, pass 20000, stop 24000, ripple 0.1 dB, atten 100 dB. For SDR-IQ. Pass 0.375. Stop 0.45000.
|
||||||
double quiskFilt53D1Coefs[55] = { 0.000318858391362187, 0.001662116369387873, -0.000065237353639775, -0.000854885424783294,
|
double quiskFilt53D1Coefs[55] = { 0.000318858391362187, 0.001662116369387873, -0.000065237353639775, -0.000854885424783294,
|
||||||
0.001693126335254083, -0.002034310867663472, 0.001354648789538254, 0.000509693525155950, -0.003079059747940221, 0.005242349925004462,
|
0.001693126335254083, -0.002034310867663472, 0.001354648789538254, 0.000509693525155950, -0.003079059747940221, 0.005242349925004462,
|
||||||
-0.005626507739484119, 0.003235398612833476, 0.001883583294529978, -0.008273775245721798, 0.013282431836060808, -0.013924391581141530,
|
-0.005626507739484119, 0.003235398612833476, 0.001883583294529978, -0.008273775245721798, 0.013282431836060808, -0.013924391581141530,
|
||||||
@@ -426,7 +426,7 @@ double quiskFilt53D1Coefs[55] = { 0.000318858391362187, 0.001662116369387873, -0
|
|||||||
-0.003079059747940221, 0.000509693525155950, 0.001354648789538254, -0.002034310867663472, 0.001693126335254083, -0.000854885424783294,
|
-0.003079059747940221, 0.000509693525155950, 0.001354648789538254, -0.002034310867663472, 0.001693126335254083, -0.000854885424783294,
|
||||||
-0.000065237353639775, 0.001662116369387873, 0.000318858391362187 } ;
|
-0.000065237353639775, 0.001662116369387873, 0.000318858391362187 } ;
|
||||||
|
|
||||||
// Sample 53333 Hz, pass 10000, stop 12000, ripple 0.5 dB, atten 100 dB. For SDR-IQ. Stop 0.22500.
|
// Sample 53333 Hz, pass 10000, stop 12000, ripple 0.5 dB, atten 100 dB. For SDR-IQ. Pass 0.1875. Stop 0.22500.
|
||||||
double quiskFilt53D2Coefs[93] = { 0.000107966318729964, 0.000421475598772498, 0.000731343669484184, 0.000328611865414609,
|
double quiskFilt53D2Coefs[93] = { 0.000107966318729964, 0.000421475598772498, 0.000731343669484184, 0.000328611865414609,
|
||||||
-0.001457272471508304, -0.004143803290279123, -0.005736802939367581, -0.004303861070721504, -0.000415676176666607,
|
-0.001457272471508304, -0.004143803290279123, -0.005736802939367581, -0.004303861070721504, -0.000415676176666607,
|
||||||
0.002561521351129964, 0.001705074918770091, -0.001794491466202784, -0.003398011312785532, -0.000595401366072839,
|
0.002561521351129964, 0.001705074918770091, -0.001794491466202784, -0.003398011312785532, -0.000595401366072839,
|
||||||
@@ -603,7 +603,7 @@ double quiskAudio48p6Coefs[71] = { 0.000324677779792651, 0.001038192405843851, 0
|
|||||||
0.001132278163758895, 0.002811674671240099, 0.002938055116822665, 0.002082337464742640, 0.001038192405843851,
|
0.001132278163758895, 0.002811674671240099, 0.002938055116822665, 0.002082337464742640, 0.001038192405843851,
|
||||||
0.000324677779792651} ;
|
0.000324677779792651} ;
|
||||||
|
|
||||||
// Sample 96 kHz, pass 6, stop 40, ripple 0.1 dB, atten 100 dB. Stop 0.41667.
|
// Sample 96 kHz, pass 6, stop 40, ripple 0.1 dB, atten 100 dB. Pass 0.0625. Stop 0.41667.
|
||||||
double quiskAudio96Coefs[11] = { -0.004756607210954994, -0.022893760473075838, -0.023609616122334920,
|
double quiskAudio96Coefs[11] = { -0.004756607210954994, -0.022893760473075838, -0.023609616122334920,
|
||||||
0.078161168368381481, 0.277880235815005590, 0.388489826271597950, 0.277880235815005590,
|
0.078161168368381481, 0.277880235815005590, 0.388489826271597950, 0.277880235815005590,
|
||||||
0.078161168368381481, -0.023609616122334920, -0.022893760473075838, -0.004756607210954994} ;
|
0.078161168368381481, -0.023609616122334920, -0.022893760473075838, -0.004756607210954994} ;
|
||||||
@@ -663,7 +663,7 @@ double quiskAudioFmHpCoefs[309] = { 0.004847574947800705, -0.001211970386842814,
|
|||||||
-0.000365311474947399, -0.000434052387522716, -0.000504808183463150, -0.000579117971409598, -0.000658731927598885, -0.000745515693376396,
|
-0.000365311474947399, -0.000434052387522716, -0.000504808183463150, -0.000579117971409598, -0.000658731927598885, -0.000745515693376396,
|
||||||
-0.000841696354699768, -0.000949749961336003, -0.001072277374239886, -0.001211970386842814, 0.004847574947800705} ;
|
-0.000841696354699768, -0.000949749961336003, -0.001072277374239886, -0.001211970386842814, 0.004847574947800705} ;
|
||||||
|
|
||||||
// Sample 24 kHz, pass 3, stop 4, ripple 0.1 dB, atten 100 dB. Stop 0.166667.
|
// Sample 24 kHz, pass 3, stop 4, ripple 0.1 dB, atten 100 dB. Pass 0.125. Stop 0.166667.
|
||||||
double quiskAudio24p3Coefs[100] = { 0.000040101217607380, 0.000144142945632828, 0.000313272623844654, 0.000472482653992188,
|
double quiskAudio24p3Coefs[100] = { 0.000040101217607380, 0.000144142945632828, 0.000313272623844654, 0.000472482653992188,
|
||||||
0.000478145332060253, 0.000196098255702949, -0.000370577202968848, -0.000996638165520275, -0.001297816111521122,
|
0.000478145332060253, 0.000196098255702949, -0.000370577202968848, -0.000996638165520275, -0.001297816111521122,
|
||||||
-0.000964536466628102, -0.000042557313824444, 0.000962997869327471, 0.001323534221298773, 0.000594056697947640,
|
-0.000964536466628102, -0.000042557313824444, 0.000962997869327471, 0.001323534221298773, 0.000594056697947640,
|
||||||
|
|||||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,221 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="quisk.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="36.5"
|
||||||
|
inkscape:cy="265"
|
||||||
|
inkscape:window-width="1854"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#c0c276;fill-opacity:1;stroke:#7180ff;stroke-width:48.671;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect556"
|
||||||
|
width="341.84088"
|
||||||
|
height="349.28082"
|
||||||
|
x="62.823105"
|
||||||
|
y="39.435188"
|
||||||
|
ry="63.709782" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#7180ff;stroke-width:43.2582;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3352"
|
||||||
|
d="m 193.20147,396.22476 c 6.34642,4.65885 12.2534,9.94281 18.02477,15.29408 7.66447,7.60025 15.04758,15.4729 22.82647,22.96045 6.77582,6.4671 13.60289,12.87487 20.7907,18.88349 12.48185,9.03783 26.6187,15.49278 41.24908,20.07891 13.25326,2.83808 26.93926,1.98796 40.38664,1.44647 9.20443,-0.51765 18.4186,-0.74315 27.63591,-0.83235 5.72468,-0.0231 11.44941,-0.0285 17.17409,-0.009 6.5518,0.45377 13.10409,0.8185 19.62685,1.59571 0,0 -12.83677,-9.54278 -12.83677,-9.54278 v 0 c -6.57664,-0.66042 -13.17806,-1.05429 -19.77266,-1.4853 -5.6652,0.0193 -11.33079,0.0283 -16.99467,0.16838 -9.18212,0.18217 -18.37151,0.31632 -27.53553,0.96497 -13.36116,0.68668 -27.00081,1.48715 -40.0815,-1.91133 -5.00213,-1.62118 -9.82704,-3.39549 -14.63078,-5.53945 -1.63493,-0.7297 -6.26911,-3.36104 -4.89255,-2.21625 2.66168,2.21356 5.85064,3.70365 8.82036,5.48275 1.18824,0.71182 -2.31609,-1.51997 -3.47415,-2.27995 -7.27169,-6.00427 -14.22221,-12.36521 -21.05126,-18.86768 -7.76318,-7.43952 -15.14957,-15.24359 -22.67391,-22.91764 -5.64713,-5.35952 -11.55857,-10.4455 -17.53983,-15.42662 0,0 -15.05126,-5.84659 -15.05126,-5.84659 z" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:17.3033;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 91.391053,213.41065 14.719477,-17.25897 11.13289,28.30763 12.1198,-17.78253 21.1946,-4.54944 18.44448,16.54887 12.64335,-45.07916 6.78807,-41.91379 11.53004,-41.685155 12.62529,26.532375 11.28934,37.31624 2.38907,28.97559 7.74488,35.33638 30.5342,-4.30873 22.19957,-13.63027 4.61563,-22.8555 13.28124,-96.170083 17.96907,92.980663 3.0811,45.22358 24.94369,-9.63446 10.832,-20.40026 14.95416,42.66002"
|
||||||
|
id="path15623"
|
||||||
|
sodipodi:nodetypes="cccccccccccccccccccccc" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect16404"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="86.571724"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-cx="107.539"
|
||||||
|
inkscape:tile-cy="302.5"
|
||||||
|
inkscape:tile-w="16"
|
||||||
|
inkscape:tile-h="109"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18731"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="103.87501"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18733"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="121.17827"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18735"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="138.48155"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18737"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="155.78482"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#dc34c6;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18739"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="173.08809"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#fffa41;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18741"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="190.39134"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d20ac7;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18743"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="207.69463"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18745"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="224.99789"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<use
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
inkscape:tiled-clone-of="#rect16404"
|
||||||
|
xlink:href="#rect16404"
|
||||||
|
transform="translate(155.72944)"
|
||||||
|
id="use18747"
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18749"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="259.60446"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#b931b2;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18751"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="276.90775"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#e7ec2e;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18753"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="294.21097"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#a735a6;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18755"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="311.51425"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18757"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="328.81754"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18759"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="346.12079"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<rect
|
||||||
|
style="fill:#3e4fa1;fill-opacity:1;stroke:none;stroke-width:34.8847;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="use18761"
|
||||||
|
width="17.303272"
|
||||||
|
height="117.87855"
|
||||||
|
x="363.42407"
|
||||||
|
y="246.9482"
|
||||||
|
inkscape:tile-x0="99.539001"
|
||||||
|
inkscape:tile-y0="248" />
|
||||||
|
<path
|
||||||
|
id="path12957"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#7180ff;stroke-width:48.671;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 404.54932,300.57496 v 23.76192 c 0,35.29519 -28.4146,63.70978 -63.70977,63.70978 H 126.41823 c -35.295185,0 -63.709785,-28.41459 -63.709785,-63.70978 v -23.19942" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "quisk"
|
name = "quisk"
|
||||||
version = "4.2.53"
|
version = "4.2.55"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
description = 'QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.'
|
description = 'QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.'
|
||||||
license = "GPL-2.0-or-later"
|
license = "GPL-2.0-or-later"
|
||||||
@@ -25,6 +25,7 @@ classifiers = [
|
|||||||
'Programming Language :: C',
|
'Programming Language :: C',
|
||||||
'Topic :: Communications :: Ham Radio',
|
'Topic :: Communications :: Ham Radio',
|
||||||
]
|
]
|
||||||
|
dynamic = ["readme"]
|
||||||
|
|
||||||
[project.gui-scripts]
|
[project.gui-scripts]
|
||||||
quisk = "quisk.quisk:main"
|
quisk = "quisk.quisk:main"
|
||||||
@@ -35,7 +36,12 @@ Documentation = "https://james.ahlstrom.name/quisk/docs.html"
|
|||||||
Repository = "https://github.com/jimahlstrom/quisk"
|
Repository = "https://github.com/jimahlstrom/quisk"
|
||||||
Changelog = "https://james.ahlstrom.name/quisk/CHANGELOG.txt"
|
Changelog = "https://james.ahlstrom.name/quisk/CHANGELOG.txt"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.package-data]
|
||||||
where = ["."]
|
"quisk.icons" = ["*"]
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = {"quisk" = "."}
|
||||||
|
packages = ["quisk", "quisk.n2adr", "quisk.softrock", "quisk.freedvpkg",
|
||||||
|
"quisk.hermes", "quisk.hiqsdr", "quisk.afedrinet", "quisk.soapypkg",
|
||||||
|
"quisk.sdrmicronpkg", "quisk.perseuspkg", "quisk.ac2yd", "quisk.multuspkg"]
|
||||||
|
|
||||||
|
|||||||
@@ -742,6 +742,7 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
|||||||
self.vfo = "Main"
|
self.vfo = "Main"
|
||||||
self.split_mode = 0
|
self.split_mode = 0
|
||||||
self.split_vfo = 'VFO'
|
self.split_vfo = 'VFO'
|
||||||
|
self.lock_mode = 0
|
||||||
h = self.Handlers = {}
|
h = self.Handlers = {}
|
||||||
h[''] = self.ErrProtocol
|
h[''] = self.ErrProtocol
|
||||||
h['dump_state'] = self.DumpState
|
h['dump_state'] = self.DumpState
|
||||||
@@ -765,6 +766,8 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
|||||||
h['set_split_freq'] = self.SetSplitFreq
|
h['set_split_freq'] = self.SetSplitFreq
|
||||||
h['get_split_vfo'] = self.GetSplitVfo
|
h['get_split_vfo'] = self.GetSplitVfo
|
||||||
h['set_split_vfo'] = self.SetSplitVfo
|
h['set_split_vfo'] = self.SetSplitVfo
|
||||||
|
h['get_lock_mode'] = self.GetLockMode
|
||||||
|
h['set_lock_mode'] = self.SetLockMode
|
||||||
self.MakeDumpState()
|
self.MakeDumpState()
|
||||||
def MakeDumpState(self):
|
def MakeDumpState(self):
|
||||||
dump_state = []
|
dump_state = []
|
||||||
@@ -1009,7 +1012,8 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
|||||||
self.ErrParam()
|
self.ErrParam()
|
||||||
else:
|
else:
|
||||||
freq = int(freq + 0.5)
|
freq = int(freq + 0.5)
|
||||||
self.app.ChangeRxTxFrequency(freq, None)
|
# Like a real radio, keep the mode. Hamlib caches the mode, so the client would not notice a mode change.
|
||||||
|
self.app.ChangeRxTxFrequency(freq, None, keep_mode=True)
|
||||||
def GetSplitFreq(self): # The Tx Frequency
|
def GetSplitFreq(self): # The Tx Frequency
|
||||||
self.Reply('TX Frequency', self.app.txFreq + self.app.VFO, 0)
|
self.Reply('TX Frequency', self.app.txFreq + self.app.VFO, 0)
|
||||||
def SetSplitFreq(self): # The Tx Frequency
|
def SetSplitFreq(self): # The Tx Frequency
|
||||||
@@ -1022,7 +1026,7 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
|||||||
else:
|
else:
|
||||||
freq = int(freq + 0.5)
|
freq = int(freq + 0.5)
|
||||||
# This always controls the Tx frequency
|
# This always controls the Tx frequency
|
||||||
self.app.ChangeRxTxFrequency(None, freq)
|
self.app.ChangeRxTxFrequency(None, freq, keep_mode=True)
|
||||||
def GetSplitVfo(self):
|
def GetSplitVfo(self):
|
||||||
self.Reply('Split', self.split_mode, 'TX VFO', self.split_vfo, 0)
|
self.Reply('Split', self.split_mode, 'TX VFO', self.split_vfo, 0)
|
||||||
def SetSplitVfo(self):
|
def SetSplitVfo(self):
|
||||||
@@ -1107,6 +1111,22 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
|||||||
Lab = label
|
Lab = label
|
||||||
diff = df
|
diff = df
|
||||||
self.app.filterButns.SetLabel(Lab, True)
|
self.app.filterButns.SetLabel(Lab, True)
|
||||||
|
# The code for the lock_mode and keep_mode was supplied by Karl Heinz - K5KHK. Thanks Karl!
|
||||||
|
def GetLockMode(self):
|
||||||
|
# Hamlib netrigctl always reads two lines, so an error reply "RPRT -4" makes it wait for its 10 second timeout.
|
||||||
|
# Reply like rigctld with the lock value and then "RPRT 0".
|
||||||
|
if self.extended:
|
||||||
|
self.Reply('Locked', self.lock_mode, 0)
|
||||||
|
else:
|
||||||
|
self.Send("%d\nRPRT 0\n" % self.lock_mode)
|
||||||
|
def SetLockMode(self):
|
||||||
|
lock = self.GetParamNumber()
|
||||||
|
try:
|
||||||
|
self.lock_mode = int(lock)
|
||||||
|
except:
|
||||||
|
self.ErrParam()
|
||||||
|
else:
|
||||||
|
self.Reply(0)
|
||||||
def GetVfo(self):
|
def GetVfo(self):
|
||||||
self.Reply('VFO', self.vfo, 0)
|
self.Reply('VFO', self.vfo, 0)
|
||||||
def SetVfo(self):
|
def SetVfo(self):
|
||||||
@@ -6136,7 +6156,7 @@ class App(wx.App):
|
|||||||
self.freqDisplay.Display(self.txFreq + self.VFO)
|
self.freqDisplay.Display(self.txFreq + self.VFO)
|
||||||
self.fldigi_new_freq = self.txFreq + self.VFO
|
self.fldigi_new_freq = self.txFreq + self.VFO
|
||||||
return change
|
return change
|
||||||
def ChangeRxTxFrequency(self, rx_freq=None, tx_freq=None):
|
def ChangeRxTxFrequency(self, rx_freq=None, tx_freq=None, keep_mode=False):
|
||||||
if not self.split_rxtx and not tx_freq:
|
if not self.split_rxtx and not tx_freq:
|
||||||
tx_freq = rx_freq
|
tx_freq = rx_freq
|
||||||
if tx_freq:
|
if tx_freq:
|
||||||
@@ -6147,7 +6167,7 @@ class App(wx.App):
|
|||||||
else: # Change the VFO
|
else: # Change the VFO
|
||||||
vfo = (tx_freq // 5000) * 5000 - 5000
|
vfo = (tx_freq // 5000) * 5000 - 5000
|
||||||
tune = tx_freq - vfo
|
tune = tx_freq - vfo
|
||||||
self.BandFromFreq(tx_freq)
|
self.BandFromFreq(tx_freq, keep_mode)
|
||||||
self.ChangeHwFrequency(tune, vfo, 'FreqEntry')
|
self.ChangeHwFrequency(tune, vfo, 'FreqEntry')
|
||||||
if rx_freq and self.split_rxtx: # Frequency must be on-screen
|
if rx_freq and self.split_rxtx: # Frequency must be on-screen
|
||||||
tune = rx_freq - self.VFO
|
tune = rx_freq - self.VFO
|
||||||
@@ -6337,7 +6357,8 @@ class App(wx.App):
|
|||||||
self.pttButton.Enable(False)
|
self.pttButton.Enable(False)
|
||||||
else:
|
else:
|
||||||
self.pttButton.Enable(True)
|
self.pttButton.Enable(True)
|
||||||
def BandFromFreq(self, frequency): # Change to a new band based on the frequency
|
def BandFromFreq(self, frequency, keep_mode=False): # Change to a new band based on the frequency
|
||||||
|
# If keep_mode is True, do not change to the mode saved for the new band.
|
||||||
if self.screen == self.bandscope_screen:
|
if self.screen == self.bandscope_screen:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -6359,7 +6380,8 @@ class App(wx.App):
|
|||||||
vfo, tune, mode = self.bandState[band]
|
vfo, tune, mode = self.bandState[band]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
vfo, tune, mode = (0, 0, 'LSB')
|
vfo, tune, mode = (0, 0, 'LSB')
|
||||||
self.modeButns.SetLabel(mode, True)
|
if not keep_mode:
|
||||||
|
self.modeButns.SetLabel(mode, True)
|
||||||
self.ChangeBand(band)
|
self.ChangeBand(band)
|
||||||
break
|
break
|
||||||
def ChangeBand(self, band):
|
def ChangeBand(self, band):
|
||||||
|
|||||||
@@ -1508,6 +1508,19 @@ remote_radio_ip = ""
|
|||||||
# It is only necessary to enter it once on each computer.
|
# It is only necessary to enter it once on each computer.
|
||||||
remote_radio_password = ""
|
remote_radio_password = ""
|
||||||
|
|
||||||
|
## remote_radio_mic_jitter_ms Mic buffer msec for remote radio, integer
|
||||||
|
# This is set on the remote radio, not the control head. The network can hold the control
|
||||||
|
# head's microphone packets for 100 msec or more and then deliver them in a burst, which is
|
||||||
|
# common over Wi-Fi. The transmit audio then has gaps. This buffers the microphone audio
|
||||||
|
# for the given time before transmitting it, so the bursts are smoothed out, at the cost
|
||||||
|
# of that much more transmit delay. Zero turns the buffer off, which is best on a wired
|
||||||
|
# network. If needed, try 150 msec. The maximum is 500. A change takes effect when the
|
||||||
|
# next control head connects.
|
||||||
|
remote_radio_mic_jitter_ms = 0
|
||||||
|
#remote_radio_mic_jitter_ms = 100
|
||||||
|
#remote_radio_mic_jitter_ms = 150
|
||||||
|
#remote_radio_mic_jitter_ms = 250
|
||||||
|
|
||||||
## k4_tcp_ip IP address for K4 TCP, text
|
## k4_tcp_ip IP address for K4 TCP, text
|
||||||
# This is the Quisk IP address for the TCP server implementing K4 commands.
|
# This is the Quisk IP address for the TCP server implementing K4 commands.
|
||||||
k4_tcp_ip = ""
|
k4_tcp_ip = ""
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ N1MM+ and software that uses Hamlib.
|
|||||||
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg',
|
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg',
|
||||||
'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd', 'quisk.multuspkg'],
|
'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd', 'quisk.multuspkg'],
|
||||||
package_dir = {'quisk' : '.'},
|
package_dir = {'quisk' : '.'},
|
||||||
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll']},
|
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll'], 'quisk' : ['icons/*']},
|
||||||
entry_points = {'gui_scripts' : ['quisk = quisk.quisk:main', 'quisk_vna = quisk.quisk_vna:main']},
|
entry_points = {'gui_scripts' : ['quisk = quisk.quisk:main', 'quisk_vna = quisk.quisk_vna:main']},
|
||||||
ext_modules = Modules,
|
ext_modules = Modules,
|
||||||
provides = ['quisk'],
|
provides = ['quisk'],
|
||||||
|
|||||||
@@ -80,9 +80,14 @@ struct ClientData { // data for each client
|
|||||||
int audio_stream_channels;
|
int audio_stream_channels;
|
||||||
int audio_stream_samples;
|
int audio_stream_samples;
|
||||||
int audio_stream_bytes_per_sample;
|
int audio_stream_bytes_per_sample;
|
||||||
|
// Rx stream decimation filters
|
||||||
struct quisk_cHB45Filter HalfBand1;
|
struct quisk_cHB45Filter HalfBand1;
|
||||||
struct quisk_cFilter filtDecim3;
|
struct quisk_cFilter filtDecim3;
|
||||||
struct quisk_cFilter filtDecim48to24;
|
struct quisk_cFilter filtDecim48to24;
|
||||||
|
// Tx stream interpolation filters
|
||||||
|
struct quisk_cHB45Filter HalfBandTx1;
|
||||||
|
struct quisk_cHB45Filter HalfBandTx2;
|
||||||
|
struct quisk_cFilter filtTxInterp3;
|
||||||
int send_IQ_stream;
|
int send_IQ_stream;
|
||||||
int IQ_stream_samplerate;
|
int IQ_stream_samplerate;
|
||||||
} ;
|
} ;
|
||||||
@@ -138,6 +143,9 @@ struct _StreamTypes StreamTypes[NUM_STREAM_TYPES] = {
|
|||||||
{"lineout ", 0}
|
{"lineout ", 0}
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
static int interpolate_tx_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx);
|
||||||
|
static int decimate_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx);
|
||||||
|
|
||||||
static void PrintStream(const char * msg, size_t size, int always)
|
static void PrintStream(const char * msg, size_t size, int always)
|
||||||
{
|
{
|
||||||
struct _Stream * pt = (struct _Stream *)msg;
|
struct _Stream * pt = (struct _Stream *)msg;
|
||||||
@@ -203,6 +211,7 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
|||||||
for (i = 0; i < tci_clients_count; i++) {
|
for (i = 0; i < tci_clients_count; i++) {
|
||||||
if (tci_clients_list[i] == client) {
|
if (tci_clients_list[i] == client) {
|
||||||
ctx->send_Rx_audio_stream = 1;
|
ctx->send_Rx_audio_stream = 1;
|
||||||
|
// Rx stream filters
|
||||||
memset(&ctx->HalfBand1,
|
memset(&ctx->HalfBand1,
|
||||||
0, sizeof(struct quisk_cHB45Filter));
|
0, sizeof(struct quisk_cHB45Filter));
|
||||||
quisk_filt_cInit(&ctx->filtDecim3,
|
quisk_filt_cInit(&ctx->filtDecim3,
|
||||||
@@ -361,6 +370,13 @@ static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
|||||||
tci_tx_audio_samples = 0;
|
tci_tx_audio_samples = 0;
|
||||||
tci_tx_audio_rate = ctx->audio_stream_samplerate;
|
tci_tx_audio_rate = ctx->audio_stream_samplerate;
|
||||||
pthread_mutex_unlock(&tx_buffer_mutex);
|
pthread_mutex_unlock(&tx_buffer_mutex);
|
||||||
|
// Tx stream filters
|
||||||
|
memset(&ctx->HalfBandTx1,
|
||||||
|
0, sizeof(struct quisk_cHB45Filter));
|
||||||
|
memset(&ctx->HalfBandTx2,
|
||||||
|
0, sizeof(struct quisk_cHB45Filter));
|
||||||
|
quisk_filt_cInit(&ctx->filtTxInterp3,
|
||||||
|
quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (client == tci_tx_audio_client) {
|
else if (client == tci_tx_audio_client) {
|
||||||
@@ -426,7 +442,7 @@ static void onopen(ws_cli_conn_t client)
|
|||||||
QuiskPrintf("TCI *Connection opened, addr: %s\n", cli);
|
QuiskPrintf("TCI *Connection opened, addr: %s\n", cli);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendframe_txt(client, "protocol:ESDR,1.4;");
|
sendframe_txt(client, "protocol:ExpertSDR3,2.0;");
|
||||||
//sendframe_txt(client, "vfo_limits:30000,30000000;"); Not known due to transverters
|
//sendframe_txt(client, "vfo_limits:30000,30000000;"); Not known due to transverters
|
||||||
int if_limit = quisk_sound_state.sample_rate / 2;
|
int if_limit = quisk_sound_state.sample_rate / 2;
|
||||||
snprintf(command, TCI_COMMAND_SIZE, "if_limits:%d,%d;", - if_limit, if_limit);
|
snprintf(command, TCI_COMMAND_SIZE, "if_limits:%d,%d;", - if_limit, if_limit);
|
||||||
@@ -480,12 +496,14 @@ static void onclose(ws_cli_conn_t client)
|
|||||||
static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t size, int type)
|
static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t size, int type)
|
||||||
{
|
{
|
||||||
char echo_copy[TCI_RX_BUF_SIZE];
|
char echo_copy[TCI_RX_BUF_SIZE];
|
||||||
complex double sample;
|
int n;
|
||||||
|
static complex double * cCopy = NULL;
|
||||||
|
static int cCopy_size = 0;
|
||||||
|
struct ClientData * ctx = ws_get_connection_context(client);
|
||||||
|
|
||||||
if (type == WS_FR_OP_TXT) {
|
if (type == WS_FR_OP_TXT) {
|
||||||
if (size > TCI_RX_BUF_SIZE / 2)
|
if (size > TCI_RX_BUF_SIZE / 2)
|
||||||
return; // prevent overflow
|
return; // prevent overflow
|
||||||
struct ClientData * ctx = ws_get_connection_context(client);
|
|
||||||
if (ctx->msg_length + size >= TCI_RX_BUF_SIZE - 2)
|
if (ctx->msg_length + size >= TCI_RX_BUF_SIZE - 2)
|
||||||
ctx->msg_length = 0; // prevent overflow
|
ctx->msg_length = 0; // prevent overflow
|
||||||
memcpy(ctx->msg_buf + ctx->msg_length, msg, size); // append msg to buffer
|
memcpy(ctx->msg_buf + ctx->msg_length, msg, size); // append msg to buffer
|
||||||
@@ -519,7 +537,6 @@ static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t s
|
|||||||
const unsigned char * vpt2;
|
const unsigned char * vpt2;
|
||||||
const unsigned char * vpt2_end;
|
const unsigned char * vpt2_end;
|
||||||
int count2;
|
int count2;
|
||||||
int buf_start=0, buf_end;
|
|
||||||
|
|
||||||
if (verbose >= 3) {
|
if (verbose >= 3) {
|
||||||
QuiskPrintf("\n\nMSG size %ld length %d\n 0 ", size, pt->length);
|
QuiskPrintf("\n\nMSG size %ld length %d\n 0 ", size, pt->length);
|
||||||
@@ -544,15 +561,19 @@ static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pt->type == TX_AUDIO_STREAM) {
|
if (pt->type == TX_AUDIO_STREAM) {
|
||||||
|
n = pt->length * 7 + 100; // Make room for 6X interpolation
|
||||||
|
if (cCopy_size < n) {
|
||||||
|
if (cCopy)
|
||||||
|
free(cCopy);
|
||||||
|
cCopy_size = n;
|
||||||
|
cCopy = (complex double *)malloc(cCopy_size * sizeof(complex double));
|
||||||
|
}
|
||||||
if (pt->length > 0 && client == tci_tx_audio_client) {
|
if (pt->length > 0 && client == tci_tx_audio_client) {
|
||||||
pthread_mutex_lock(&tx_buffer_mutex);
|
n = 0;
|
||||||
if (verbose >= 2)
|
|
||||||
buf_start = CircularBuffer(0, &sample, 0, 0);
|
|
||||||
switch (TxChrono.format) {
|
switch (TxChrono.format) {
|
||||||
case TCI_INT16:
|
case TCI_INT16:
|
||||||
break;
|
break;
|
||||||
case TCI_FLOAT32:
|
case TCI_FLOAT32:
|
||||||
// Version 1.4 does not return the number of channels. We assume two channels.
|
|
||||||
// pt->length is the number of floats
|
// pt->length is the number of floats
|
||||||
while (vpt < vpt_end && count < pt->length) {
|
while (vpt < vpt_end && count < pt->length) {
|
||||||
re = *(float *)vpt;
|
re = *(float *)vpt;
|
||||||
@@ -566,18 +587,16 @@ static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t s
|
|||||||
else {
|
else {
|
||||||
im = re;
|
im = re;
|
||||||
}
|
}
|
||||||
sample = (re + I * im) * (CLIP32 / 2);
|
cCopy[n++] = (re + I * im) * (CLIP32 / 2);
|
||||||
CircularBuffer(0, &sample, 0, 1);
|
|
||||||
}
|
}
|
||||||
if (verbose && count != pt->length)
|
if (verbose && count != pt->length)
|
||||||
QuiskPrintf("TCI *TX count %ld %d %d\n", size, count, pt->length);
|
QuiskPrintf("TCI *TX count %ld %d %d\n", size, count, pt->length);
|
||||||
|
n = interpolate_tx_audio_stream(cCopy, n, ctx);
|
||||||
|
pthread_mutex_lock(&tx_buffer_mutex);
|
||||||
|
CircularBuffer(0, cCopy, 0, n);
|
||||||
|
pthread_mutex_unlock(&tx_buffer_mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (verbose >= 2) {
|
|
||||||
buf_end = CircularBuffer(0, &sample, 0, 0);
|
|
||||||
QuiskPrintf("TCI Tx buffer add %4d start %5d end %5d\n", count, buf_start, buf_end);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&tx_buffer_mutex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (verbose)
|
if (verbose)
|
||||||
@@ -634,6 +653,29 @@ static int decimate_audio_stream(complex double * cSamples, int nSamples, struct
|
|||||||
return nSamples;
|
return nSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int interpolate_tx_audio_stream(complex double * cSamples, int nSamples, struct ClientData * ctx)
|
||||||
|
{
|
||||||
|
switch(tci_tx_audio_rate) { // Ending audio rate is 48000
|
||||||
|
case 48000:
|
||||||
|
break;
|
||||||
|
case 24000:
|
||||||
|
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx1);
|
||||||
|
break;
|
||||||
|
case 12000:
|
||||||
|
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx1);
|
||||||
|
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx2);
|
||||||
|
break;
|
||||||
|
case 8000:
|
||||||
|
nSamples = quisk_cInterpolate(cSamples, nSamples, &ctx->filtTxInterp3, 3);
|
||||||
|
nSamples = quisk_cInterp2HB45(cSamples, nSamples, &ctx->HalfBandTx1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nSamples = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return nSamples;
|
||||||
|
}
|
||||||
|
|
||||||
void tci_send_audio(complex double * cSamples, int nSamples) // called from the sound thread
|
void tci_send_audio(complex double * cSamples, int nSamples) // called from the sound thread
|
||||||
{
|
{
|
||||||
int i, n;
|
int i, n;
|
||||||
|
|||||||