diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2b1390d..8039be2 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,12 @@ +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 /quisk/icons/quisk.ico. +The Linux icon is quisk/icons/quisk.svg. On Linux, change the quisk.desktop file to +Icon=/quisk/icons/quisk.svg. + Quisk Version 4.2.53 June 2026 =============================== I added TCI support for Software Defined Connectors CW skimmer. Please test. diff --git a/__init__.py b/__init__.py index 002f4aa..aaad8d6 100644 --- a/__init__.py +++ b/__init__.py @@ -1,2 +1,2 @@ -#Quisk version 4.2.53 +#Quisk version 4.2.54 from .quisk import main diff --git a/docs.html b/docs.html index 631c2bb..50a3423 100644 --- a/docs.html +++ b/docs.html @@ -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 are curious, Quisk and its Python source files are installed in "your_install_directory\Lib\site-packages\quisk". 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 + "your_install_directory\Lib\site-packages\quisk\icons\quisk.ico".


@@ -389,6 +391,7 @@ make

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. +The quisk icon is the file quisk/icons/quisk.svg. Never run Quisk with root privelege by using sudo. Fix the permission problem instead. 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. diff --git a/filters.h b/filters.h index 3caf377..7735d75 100644 --- a/filters.h +++ b/filters.h @@ -414,7 +414,7 @@ double quiskFilt111D4Coefs[196] = { 0.000022860383861326, 0.000070265173417164, 0.000878636067621920, 0.000694724116305488, 0.000487289901409396, 0.000301317969124407, 0.000160689233204503, 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, 0.001693126335254083, -0.002034310867663472, 0.001354648789538254, 0.000509693525155950, -0.003079059747940221, 0.005242349925004462, -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.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, -0.001457272471508304, -0.004143803290279123, -0.005736802939367581, -0.004303861070721504, -0.000415676176666607, 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.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, 0.078161168368381481, 0.277880235815005590, 0.388489826271597950, 0.277880235815005590, 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.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, 0.000478145332060253, 0.000196098255702949, -0.000370577202968848, -0.000996638165520275, -0.001297816111521122, -0.000964536466628102, -0.000042557313824444, 0.000962997869327471, 0.001323534221298773, 0.000594056697947640, diff --git a/icons/quisk.ico b/icons/quisk.ico new file mode 100644 index 0000000..331ed93 Binary files /dev/null and b/icons/quisk.ico differ diff --git a/icons/quisk.svg b/icons/quisk.svg new file mode 100644 index 0000000..76b9f08 --- /dev/null +++ b/icons/quisk.svg @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/quisk_128.png b/icons/quisk_128.png new file mode 100644 index 0000000..6f16a9b Binary files /dev/null and b/icons/quisk_128.png differ diff --git a/icons/quisk_16.png b/icons/quisk_16.png new file mode 100644 index 0000000..5d793c4 Binary files /dev/null and b/icons/quisk_16.png differ diff --git a/icons/quisk_256.png b/icons/quisk_256.png new file mode 100644 index 0000000..d79a19e Binary files /dev/null and b/icons/quisk_256.png differ diff --git a/icons/quisk_32.png b/icons/quisk_32.png new file mode 100644 index 0000000..3997a8e Binary files /dev/null and b/icons/quisk_32.png differ diff --git a/icons/quisk_48.png b/icons/quisk_48.png new file mode 100644 index 0000000..611b89b Binary files /dev/null and b/icons/quisk_48.png differ diff --git a/icons/quisk_512.png b/icons/quisk_512.png new file mode 100644 index 0000000..73a3f88 Binary files /dev/null and b/icons/quisk_512.png differ diff --git a/icons/quisk_64.png b/icons/quisk_64.png new file mode 100644 index 0000000..ee8850c Binary files /dev/null and b/icons/quisk_64.png differ diff --git a/pyproject.toml b/pyproject.toml index c00e148..7dd0271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "quisk" -version = "4.2.53" +version = "4.2.54" 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" @@ -25,6 +25,7 @@ classifiers = [ 'Programming Language :: C', 'Topic :: Communications :: Ham Radio', ] +dynamic = ["readme"] [project.gui-scripts] quisk = "quisk.quisk:main" @@ -35,7 +36,12 @@ 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 = ["."] +[tool.setuptools.package-data] +"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"] diff --git a/setup.py b/setup.py index e06a76f..e9b0486 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ N1MM+ and software that uses Hamlib. 'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg', 'quisk.sdrmicronpkg', 'quisk.perseuspkg', 'quisk.ac2yd', 'quisk.multuspkg'], 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']}, ext_modules = Modules, provides = ['quisk'], diff --git a/tci.c b/tci.c index fc08691..7f4f978 100644 --- a/tci.c +++ b/tci.c @@ -80,9 +80,14 @@ struct ClientData { // data for each client int audio_stream_channels; int audio_stream_samples; int audio_stream_bytes_per_sample; + // Rx stream decimation filters struct quisk_cHB45Filter HalfBand1; struct quisk_cFilter filtDecim3; 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 IQ_stream_samplerate; } ; @@ -138,6 +143,9 @@ struct _StreamTypes StreamTypes[NUM_STREAM_TYPES] = { {"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) { 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++) { if (tci_clients_list[i] == client) { ctx->send_Rx_audio_stream = 1; + // Rx stream filters memset(&ctx->HalfBand1, 0, sizeof(struct quisk_cHB45Filter)); 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_rate = ctx->audio_stream_samplerate; 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) { @@ -426,7 +442,7 @@ static void onopen(ws_cli_conn_t client) 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 int if_limit = quisk_sound_state.sample_rate / 2; 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) { 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 (size > TCI_RX_BUF_SIZE / 2) return; // prevent overflow - struct ClientData * ctx = ws_get_connection_context(client); if (ctx->msg_length + size >= TCI_RX_BUF_SIZE - 2) ctx->msg_length = 0; // prevent overflow 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_end; int count2; - int buf_start=0, buf_end; if (verbose >= 3) { 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) { + 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) { - pthread_mutex_lock(&tx_buffer_mutex); - if (verbose >= 2) - buf_start = CircularBuffer(0, &sample, 0, 0); + n = 0; switch (TxChrono.format) { case TCI_INT16: 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 while (vpt < vpt_end && count < pt->length) { re = *(float *)vpt; @@ -566,18 +587,16 @@ static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t s else { im = re; } - sample = (re + I * im) * (CLIP32 / 2); - CircularBuffer(0, &sample, 0, 1); + cCopy[n++] = (re + I * im) * (CLIP32 / 2); } if (verbose && 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; } - 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) @@ -634,6 +653,29 @@ static int decimate_audio_stream(complex double * cSamples, int nSamples, struct 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 { int i, n;