diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3055b6b..2733dbf 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,13 @@ +Quisk Version 4.2.45 October 2025 +================================== +The new FreeDV including RADE mode is now a built-in mode of Quisk. Just press "FDV" to talk FreeDV. +In brief, install FreeDV-GUI from the web site. Then configure the usual Quisk digital input and +output that you use for digital modes. In FreeDV-GUI use the digital input and output. For the +speakers and microphone use the same ones as Quisk. Don't forget to configure your call sign in +FreeDV so it works with FreeDV Reporter. For detailed instructions see http://james.ahlstrom.name/rade.html. + +The Quisk digital output device is now always active; a DGT mode is not required. + Quisk Version 4.2.44 September 2025 ==================================== Quisk now has Pure Signal for the Hermes Lite2. This is baseband predistortion to reduce IMD products. diff --git a/__init__.py b/__init__.py index e9a2a64..24b9b9b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,2 +1,2 @@ -#Quisk version 4.2.44 +#Quisk version 4.2.45 from .quisk import main diff --git a/ac2yd/control_common.py b/ac2yd/control_common.py index 24d8bdc..9e492d0 100644 --- a/ac2yd/control_common.py +++ b/ac2yd/control_common.py @@ -395,7 +395,7 @@ class ControlCommon(BaseHardware): # This is the Hardware class for the control for idName in ("Mute", "NR2", "AGC", "Sqlch", "NB 1", "Notch", "Test 1", "Spot", "FDX", "PTT", "VOX"): self.RemoteCtlSend("%s;%d\n" % (idName, app.idName2Button[idName].GetIndex())) # Menus - for menu in (app.NB_menu, app.split_menu, app.freedv_menu, app.smeter_menu): + for menu in (app.NB_menu, app.split_menu, app.smeter_menu): if menu: for nid in menu.id2data: menu_item = menu.FindItemById(nid) diff --git a/docs.html b/docs.html index 61c4a67..50a5657 100644 --- a/docs.html +++ b/docs.html @@ -200,7 +200,8 @@ If you have Python 3 installed on your computer, just continue to use that versi On Windows Quisk requires Python 3.8, 3.9, 3.10, 3.11, 3.12 or 3.13. If you want to install a more recent version of Python, uninstall the old version first. Running multiple versions of Python is possible but can be confusing and is not necessary. -A Quisk installation is needed for each version of Python you have. +A Quisk installation is needed for each version of Python you have. If you upgrade to a newer Python +you must re-install Quisk. But your Quisk settings will be preserved.


Windows Initial Installation

diff --git a/freedv.c b/freedv.c index bf5a72e..bf04050 100644 --- a/freedv.c +++ b/freedv.c @@ -1,681 +1 @@ -#include -#include -#include -#include // Use native C99 complex type for fftw3 -#include - -#include "quisk.h" -#include "freedv.h" -#include - -int DEBUG; - -#define MAX_RECEIVERS 2 - -typedef struct { // from comp.h - float real; - float imag; -} COMP; - -struct freedv; // from freedv_api.h -typedef void (*freedv_callback_rx)(void *, char); -typedef char (*freedv_callback_tx)(void *); -/* Protocol bits are packed MSB-first */ -/* Called when a frame containing protocol data is decoded */ -typedef void (*freedv_callback_protorx)(void *, char *); -/* Called when a frame containing protocol data is to be sent */ -typedef void (*freedv_callback_prototx)(void *, char *); -/* Data packet callbacks */ -/* Called when a packet has been received */ -typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size); -/* Called when a new packet can be send */ -typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size); - -/* advanced freedv open options required by some modes */ -struct freedv_advanced { - int interleave_frames; // now unused but remains to prevent breaking API for - // legacy apps - - // parameters for FREEDV_MODE_FSK_LDPC - int M; // 2 or 4 FSK - int Rs; // Symbol rate Hz - int Fs; // Sample rate Hz - int first_tone; // Freq of first tone Hz - int tone_spacing; // Spacing between tones Hz - char *codename; // LDPC codename, from codes listed in ldpc_codes.c -}; - -#ifdef MS_WINDOWS -#include -HMODULE hLib; -#define GET_HANDLE1 hLib = LoadLibrary("libcodec2.dll") -#define GET_HANDLE2 hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll") -#define GET_HANDLE3 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll") -#define GET_HANDLE4 hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll") -#define GET_ADDR(name) (void *)GetProcAddress(hLib, name) -#define CLOSE_LIB FreeLibrary(hLib) -#else -#include -void * hLib; -#define GET_HANDLE1 hLib = dlopen("libcodec2.so", RTLD_LAZY) -#define GET_HANDLE2 hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY) -#define GET_HANDLE3 hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY) -#define GET_HANDLE4 hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY) -#define GET_ADDR(name) dlsym(hLib, name) -#define CLOSE_LIB dlclose(hLib) -#endif - -static int requested_mode = -1; // requested mode -int freedv_current_mode = -1; // the current running mode -static int quisk_freedv_squelch; -static int interleave_frames = 1; -static int freedv_version = -1; -static int quisk_set_tx_bpf = 1; -static int handle_index = -1; -// need access to these here and in quisk.c -int n_speech_sample_rate = 8000; -int n_modem_sample_rate = 8000; -int n_max_modem_samples = 1000; - -#define SPEECH_BUF_SIZE 12000 // speech buffer size increased from 3000 -#define MAX_MODEM_SAMPLES 8192 // maximum of n_max_modem_samples for any mode -#define MAX_SPEECH_SAMPLES 8192 // maximum of n_speech_samples for any mode -static struct _rx_channel{ - struct freedv * hFreedv; - short * demod_in; - int rxdata_index; - short speech_out[SPEECH_BUF_SIZE]; // output buffer - int speech_available; // number of samples in output buffer - int playing; // are we currently returning speech samples? -} rx_channel[MAX_RECEIVERS] = { 0 }; // safe initialisation to 0 - -// freedv_version is the library version number, or -// -1 no library was found -// -2 a library was found, but freedv_get_version is missing - -// FreeDV API functions: -// open, close -struct freedv * (*freedv_open)(int mode); -struct freedv * (*freedv_open_advanced)(int mode, struct freedv_advanced *adv); -void (*freedv_close)(struct freedv *freedv); -// Transmit -void (*freedv_tx)(struct freedv *freedv, short *, short *); -void (*freedv_comptx)(struct freedv *freedv, COMP *, short *); -// Receive -int (*freedv_nin)(struct freedv *freedv); -int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]); -int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]); -int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]); -// Set parameters -void (*freedv_set_callback_txt) (struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state); -void (*freedv_set_test_frames) (struct freedv *freedv, int test_frames); -void (*freedv_set_smooth_symbols) (struct freedv *freedv, int smooth_symbols); -void (*freedv_set_squelch_en) (struct freedv *freedv, int squelch_en); -void (*freedv_set_snr_squelch_thresh) (struct freedv *freedv, float snr_squelch_thresh); -void (*freedv_set_tx_bpf) (struct freedv *freedv, int val); -// Get parameters -int (*freedv_get_version)(void); -void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est); -int (*freedv_get_test_frames) (struct freedv *freedv); -int (*freedv_get_n_speech_samples) (struct freedv *freedv); -int (*freedv_get_n_max_modem_samples) (struct freedv *freedv); -int (*freedv_get_n_nom_modem_samples) (struct freedv *freedv); -int (*freedv_get_total_bits) (struct freedv *freedv); -int (*freedv_get_total_bit_errors) (struct freedv *freedv); -int (*freedv_get_modem_sample_rate) (struct freedv *freedv); -int (*freedv_get_speech_sample_rate) (struct freedv *freedv); -// -int (*freedv_get_sync) (struct freedv *freedv); -void (*freedv_set_callback_protocol) (struct freedv *freedv, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state); -void (*freedv_set_callback_data) (struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state); - -// -// checkAvxSupport -// -// This function was copied from freedv, file fdmdv2_main.cpp and tweaked for use here -// -// Tests the underlying platform for AVX support. 2020 needs AVX support to run -// in real-time, and old processors do not offer AVX support -// - -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) -#include -static int checkAvxSupport(void) -{ - - int isAvxPresent = 0; - uint32_t eax, ebx, ecx, edx; - eax = ebx = ecx = edx = 0; - __cpuid(1, eax, ebx, ecx, edx); - - if (ecx & (1<<27) && ecx & (1<<28)) { - // CPU supports XSAVE and AVX - uint32_t xcr0, xcr0_high; - asm("xgetbv" : "=a" (xcr0), "=d" (xcr0_high) : "c" (0)); - isAvxPresent = (xcr0 & 6) == 6; // AVX state saving enabled? - } - return isAvxPresent; -} -#else -static int checkAvxSupport(void) -{ - return 0; -} -#endif - -/* Called when a new packet can be sent */ -void my_datatx(void *callback_state, unsigned char *packet, size_t *size) { - *size = 0; -} - -static void GetAddrs(void) -{ - handle_index = 1; - GET_HANDLE1; - if ( ! hLib) { - handle_index = 2; - GET_HANDLE2; - if ( ! hLib) { - handle_index = 4; - GET_HANDLE4; - } - } - if (hLib) { - freedv_get_version = GET_ADDR("freedv_get_version"); - if (freedv_get_version != NULL) { - freedv_version = freedv_get_version(); - if (DEBUG) - printf("FreeDV codec2 library %d found, version %d\n", handle_index, freedv_version); - } - else { - freedv_version = -2; - if (DEBUG) - printf("FreeDV codec2 library %d found, but no freedv_get_version() !!!\n", handle_index); - CLOSE_LIB; - return; - } - } - else { - handle_index = -1; - if (DEBUG) - printf("Could not find the FreeDV codec2 shared library\n"); - return; - } - -// open, close - freedv_open = GET_ADDR("freedv_open"); - freedv_open_advanced = GET_ADDR("freedv_open_advanced"); - freedv_close = GET_ADDR("freedv_close"); -// Transmit - freedv_tx = GET_ADDR("freedv_tx"); - freedv_comptx = GET_ADDR("freedv_comptx"); -// Receive - freedv_nin = GET_ADDR("freedv_nin"); - freedv_rx = GET_ADDR("freedv_rx"); - freedv_floatrx = GET_ADDR("freedv_floatrx"); - freedv_comprx = GET_ADDR("freedv_comprx"); -// Set parameters - freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt"); - freedv_set_callback_protocol = GET_ADDR("freedv_set_callback_protocol"); - freedv_set_callback_data = GET_ADDR("freedv_set_callback_data"); - freedv_set_test_frames = GET_ADDR("freedv_set_test_frames"); - freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols"); - freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en"); - freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh"); - freedv_set_tx_bpf = GET_ADDR("freedv_set_tx_bpf"); -// Get parameters - freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats"); - freedv_get_test_frames = GET_ADDR("freedv_get_test_frames"); - freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples"); - freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples"); - freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples"); - freedv_get_total_bits = GET_ADDR("freedv_get_total_bits"); - freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors"); - freedv_get_sync = GET_ADDR("freedv_get_sync"); // requires version 11 - if( freedv_version >= 11) - freedv_get_modem_sample_rate = GET_ADDR("freedv_get_modem_sample_rate"); // requires version 11 or later - if (freedv_version >= 12) - freedv_get_speech_sample_rate = GET_ADDR("freedv_get_speech_sample_rate"); // requires version 12 - return; -} - -static int quisk_freedv_rx(short * sSamples, double * dsamples, int count, int bank) // Called from the sound thread. -{ // Input digital modulation is sSamples; decoded voice is dsamples. Each "bank" is a stream of audio. - // caller will have decimated input audio down to n_modem_sample_rate and reduced block size to match reduction - // output data will be returned at n_speech_sample_rate with corresponding block sizes - int i, nout, need, have, sync; - int n_speech_samples; - struct freedv * hF; - struct _rx_channel * pCh; - int nCountOut = 1; // In case we have a higher (or lower) value for o/p count - - if (sSamples == NULL) { // shutdown - for (i = 0; i < MAX_RECEIVERS; i++) { - if (rx_channel[i].demod_in) { - free(rx_channel[i].demod_in); - rx_channel[i].demod_in = NULL; - } - } - return 0; - } - - if (bank < 0 || bank >= MAX_RECEIVERS) - return 0; - hF = rx_channel[bank].hFreedv; - if ( ! hF) - return 0; - pCh = rx_channel + bank; - n_speech_samples = freedv_get_n_speech_samples(hF); - // if (say) input modem rate is the 2020 mode rate of 8000 sa/sec - // and output rate is the corresponding 16000 sa/sec then for an input - // block size of N, outbut 2N samples block size. - // Apply a multiplier/divisor to 'count' for the output samples count - if(n_speech_sample_rate >= n_modem_sample_rate ) { - int nRatio = n_speech_sample_rate / n_modem_sample_rate; - if( nRatio >= 1 && nRatio <= 6) - nCountOut = nRatio * count; - } - else { - int nRatio = n_modem_sample_rate / n_speech_sample_rate; - if( nRatio >= 1 && nRatio <= 6) - nCountOut = count / nRatio; - } - nout = 0; - need = freedv_nin(hF); - for (i = 0; i < count; i++) { - pCh->demod_in[pCh->rxdata_index] = sSamples[i]; - pCh->rxdata_index++; - if (pCh->rxdata_index >= need) { - if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) { // check for buffer space - have = freedv_rx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in); - if (freedv_version > 10) - sync = freedv_get_sync(hF); - else - freedv_get_modem_stats(hF, &sync, NULL); - if (freedv_current_mode == 0) { // mode 1600 - if (sync) // throw away speech if not in sync - pCh->speech_available += have; - } - else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) { - pCh->speech_available += have; // keep speech if there is space - } - else { - if (DEBUG) printf("Close to maximum in speech output buffer\n"); - } - } - else { // no space in buffer - if (DEBUG) printf("Overflow in speech output buffer\n"); - } - pCh->rxdata_index = 0; - need = freedv_nin(hF); - } - } - if ( ! pCh->playing) { - if (pCh->speech_available >= 2 * n_speech_samples) { - pCh->playing = 1; - } - else { // return zero samples - for (i = 0; i < nCountOut; i++) - dsamples[i] = 0; - //if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available); - return nCountOut; - } - } - for (nout = 0; nout < pCh->speech_available && nout < nCountOut; nout++) - dsamples[nout] = pCh->speech_out[nout] * ((double)CLIP32 / CLIP16 * 0.99); - if (nout) { - pCh->speech_available -= nout; - memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short)); - } - if ( ! pCh->speech_available) { - pCh->playing = 0; - while (nout < nCountOut) - dsamples[nout++] = 0; - } - //if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available); - return nout; -} - -static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count, int is_real) // Called from the sound thread. -{ // Input voice samples are dsamples; output digital modulation is cSamples. - int i, nout; - int n_speech_samples; - int n_nom_modem_samples; - static COMP * mod_out = NULL; - static short * speech_in = NULL; - static int speech_index=0, mod_index=0; - // g8kbb - add for 800XA - static short *real_mod_out = NULL; - - if (dsamples == NULL) { // shutdown - return 0; // nothing to do - } - if ( ! rx_channel[0].hFreedv) - return 0; - n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv); - n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv); - // find if need to output at faster rate - int nRatio = n_modem_sample_rate / n_speech_sample_rate; - if( nRatio < 1 || nRatio > 6 ) - nRatio = 1; - if (mod_out == NULL) { // initialize - mod_out = (COMP *)malloc(sizeof(COMP) * MAX_MODEM_SAMPLES); - memset(mod_out, 0, sizeof(COMP) * MAX_MODEM_SAMPLES); - speech_in = (short*)malloc(sizeof(short) * MAX_SPEECH_SAMPLES); - speech_index=0; - mod_index=0; - real_mod_out = (short *)malloc(sizeof(short) * MAX_MODEM_SAMPLES); - memset(real_mod_out, 0, sizeof(short) * MAX_MODEM_SAMPLES); - //if( DEBUG ) printf("Initialise tx buffer size = %d, i/p sample size = %d\n", n_nom_modem_samples, n_speech_samples ); - } - nout = 0; - for (i = 0; i < count; i++) { - speech_in[speech_index++] = (short)dsamples[i]; - if (speech_index >= n_speech_samples) { - // Calculate a new block, but first write out the rest of the old block - // 800xa uses real not complex - if( is_real ) { - for ( ; mod_index < n_nom_modem_samples; mod_index++) - cSamples[nout++] = real_mod_out[mod_index]; - freedv_tx(rx_channel[0].hFreedv, real_mod_out, speech_in); - } - else { - for ( ; mod_index < n_nom_modem_samples; mod_index++) - cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag; - freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in); - } - //if( DEBUG ) printf("Block processed, speech index=%d, n_speech_samples=%d, count=%d\n", speech_index, n_speech_samples, count ); - mod_index = 0; - speech_index = 0; - } - else { // write out samples - for( int j=0; j= TX_MSG_SIZE) - index = 0; - if ( ! c) { - index = 0; - c = quisk_tx_msg[index++]; - } - return c; -} - -#define RX_MSG_SIZE 80 -static char quisk_rx_msg[RX_MSG_SIZE + 1]; - -static void put_next_rx_char(void * callback_state, char ch) -{ - if (ch == '\n' || ch == '\r') - ch = ' '; - if (ch < 32 || ch > 126) // printable characters - return; - if (strlen(quisk_rx_msg) < RX_MSG_SIZE) - strncat(quisk_rx_msg, &ch, 1); -} - -PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args) // Called from the GUI thread. -{ - PyObject * txt; - - if (!PyArg_ParseTuple (args, "")) - return NULL; - txt = PyString_FromString(quisk_rx_msg); - quisk_rx_msg[0] = 0; - return txt; -} - -static void CloseFreedv(void) // Called from the GUI thread or sound thread -{ - int i; - - for (i = 0; i < MAX_RECEIVERS; i++) { - if (rx_channel[i].hFreedv) { - freedv_close(rx_channel[i].hFreedv); - rx_channel[i].hFreedv = NULL; - } - } - quisk_freedv_rx(NULL, NULL, 0, 0); - quisk_freedv_tx(NULL, NULL, 0, 0); - freedv_current_mode = -1; -} - -static int OpenFreedv(void) // Called from the GUI thread or sound thread -{ - int i; - struct freedv * hF; - - if ( ! hLib) - GetAddrs(); // Get the entry points for funtions in the codec2 library - if (DEBUG) { - switch (handle_index) { - case 1: - printf("freedv_open: system codec2 library found, version %d\n", freedv_version); - break; - case 2: - printf("freedv_open: codec2 library freedvpkg/libcodec2.dll|so found, version %d\n", freedv_version); - break; - case 3: - case 4: - printf("freedv_open: codec2 library freedvpkg/libcodec2_64 found, version %d\n", freedv_version); - break; - default: - printf("freedv_open: Could not find the FreeDV codec2 shared library\n"); - break; - } - } - if (freedv_version < 10) { - CloseFreedv(); - requested_mode = -1; - if (DEBUG) - printf("freedv_open: Failure because version is less than 10\n"); - return 0; // failure - } - if (requested_mode == FREEDV_MODE_2020) { - if (checkAvxSupport() == 0) { - CloseFreedv(); - requested_mode = -1; - if (DEBUG) - printf("freedv_open: Failure because mode 2020 requires Avx support\n"); - return 0; // failure - } - if (handle_index >= 3) { // Quisk provided codec2 - CloseFreedv(); - requested_mode = -1; - if (DEBUG) - printf("freedv_open: Failure because mode 2020 requires a system installation of codec2\n"); - return 0; // failure - } - } - - if ((requested_mode == FREEDV_MODE_700D || requested_mode == FREEDV_MODE_700E) && freedv_open_advanced) { - struct freedv_advanced adv; - memset(&adv, 0, sizeof(adv)); - adv.interleave_frames = interleave_frames; - hF = freedv_open_advanced(requested_mode, &adv); - } - else { - hF = freedv_open(requested_mode); - } - if (hF == NULL) { - CloseFreedv(); - requested_mode = -1; - return 0; // failure - } - rx_channel[0].hFreedv = hF; - quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx); - if (quisk_tx_msg[0]) - freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL); - else - freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL); - if (freedv_set_callback_protocol) - freedv_set_callback_protocol(hF, NULL, NULL, NULL); - if (freedv_set_callback_data) - freedv_set_callback_data(hF, NULL, my_datatx, NULL); - freedv_set_squelch_en(hF, quisk_freedv_squelch); - if (freedv_set_tx_bpf) - freedv_set_tx_bpf(hF, quisk_set_tx_bpf); - n_max_modem_samples = freedv_get_n_max_modem_samples(hF); - n_speech_sample_rate = 8000; - if (freedv_version >= 12 && freedv_get_speech_sample_rate) - n_speech_sample_rate = freedv_get_speech_sample_rate(hF); - n_modem_sample_rate = 8000; - if (freedv_version >= 11 && freedv_get_modem_sample_rate) - n_modem_sample_rate = freedv_get_modem_sample_rate(hF); - for (i = 0; i < MAX_RECEIVERS; i++) { - rx_channel[i].rxdata_index = 0; - rx_channel[i].speech_available = 0; - rx_channel[i].playing = 0; - if (rx_channel[i].demod_in) - free(rx_channel[i].demod_in); - rx_channel[i].demod_in = (short *)malloc(sizeof(short) * n_max_modem_samples); - if( rx_channel[i].demod_in == NULL) { - CloseFreedv(); - requested_mode = -1; - return 0; // failure - } - if (i > 0) { - rx_channel[i].hFreedv = freedv_open(requested_mode); - if (rx_channel[i].hFreedv) - freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch); - } - } - if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv)); - if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv)); - if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples); - if (DEBUG) { - if (freedv_version >= 11) printf("modem_sample_rate %d\n", n_modem_sample_rate); - if (freedv_version >= 12) printf("speech_sample_rate %d\n", n_speech_sample_rate); - } - freedv_current_mode = requested_mode; - return 1; // success -} - -void quisk_check_freedv_mode(void) -{ // see if we need to change the mode - if (requested_mode == freedv_current_mode) - return; - if (DEBUG) printf("Change in mode to %d\n", requested_mode); - CloseFreedv(); - if (requested_mode >= 0) - OpenFreedv(); - else - requested_mode = -1; -} - -PyObject * quisk_freedv_open(PyObject * self, PyObject * args) // Called from the GUI thread before freedv is open -{ - if (!PyArg_ParseTuple (args, "")) - return NULL; - return PyInt_FromLong(OpenFreedv()); -} - -PyObject * quisk_freedv_close(PyObject * self, PyObject * args) // Called from the GUI thread. -{ - if (!PyArg_ParseTuple (args, "")) - return NULL; - requested_mode = -1; // request close - Py_INCREF (Py_None); - return Py_None; -} - -PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds) // Called from the GUI thread. -{ // Call with keyword arguments ONLY to change parameters. Call before quisk_freedv_open() to set an initial mode. - int mode=-1; // Call again to change the mode. - int bpf=-1; - char * ptMsg=NULL; - static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", "interleave_frames", "set_tx_bpf", NULL} ; - - if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf)) - return NULL; - if (ptMsg) - strMcpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE); - if (bpf != -1) { - quisk_set_tx_bpf = bpf; - if (freedv_set_tx_bpf && rx_channel[0].hFreedv) - freedv_set_tx_bpf(rx_channel[0].hFreedv, quisk_set_tx_bpf); - } - if (mode == -1) { // mode was not entered - ; - } - else if (mode == FREEDV_MODE_700E && freedv_version >= 14) { - requested_mode = mode; - } - else if (mode == FREEDV_MODE_2020) { - if (checkAvxSupport() && handle_index <= 2 && freedv_version >= 13) - requested_mode = mode; - } - else if (freedv_version == 10) { - if (mode == 0) - requested_mode = mode; - } - else if (freedv_version == 11) { - if (mode <= 2) - requested_mode = mode; - } - else { - requested_mode = mode; - } - return PyInt_FromLong(requested_mode); // Return the mode -} - -PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args) // Called from the GUI thread. -{ - float snr_est = 0.0; - - if (!PyArg_ParseTuple (args, "")) - return NULL; - if (rx_channel[0].hFreedv) - freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est); - return PyFloat_FromDouble(snr_est); -} - -PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args) // Called from the GUI thread. -{ - if (!PyArg_ParseTuple (args, "")) - return NULL; - if ( ! hLib) - GetAddrs(); // Get the entry points for funtions in the codec2 library - return PyInt_FromLong(freedv_version); -} - -PyObject * quisk_freedv_set_squelch_en(PyObject * self, PyObject * args) // Called from the GUI thread. -{ - int i,state; - - if (!PyArg_ParseTuple (args, "i", &state)) - return NULL; - quisk_freedv_squelch = state; - for (i = 0; i < MAX_RECEIVERS; i++) { - if( rx_channel[i].hFreedv ) - freedv_set_squelch_en(rx_channel[i].hFreedv, state); - } - return PyInt_FromLong(state); -} - - +// This file is no longer used. diff --git a/freedv.h b/freedv.h index c894c7c..f5df7e1 100644 --- a/freedv.h +++ b/freedv.h @@ -1,24 +1 @@ -extern int freedv_current_mode; // the current FreeDV running mode - -// available speech modes -#define FREEDV_MODE_1600 0 -#define FREEDV_MODE_2400A 3 -#define FREEDV_MODE_2400B 4 -#define FREEDV_MODE_800XA 5 -#define FREEDV_MODE_700C 6 -#define FREEDV_MODE_700D 7 -#define FREEDV_MODE_700E 13 -#define FREEDV_MODE_2020 8 -#define FREEDV_MODE_2020B 16 - -// available data modes -#define FREEDV_MODE_FSK_LDPC 9 -#define FREEDV_MODE_DATAC1 10 -#define FREEDV_MODE_DATAC3 12 -#define FREEDV_MODE_DATAC0 14 -#define FREEDV_MODE_DATAC4 18 -#define FREEDV_MODE_DATAC13 19 - -// Sample rates used -#define FREEDV_FS_8000 8000 -#define FREEDV_FS_16000 16000 +// This header file is no longer used. diff --git a/freedvpkg/README.txt b/freedvpkg/README.txt index bf20708..68769c7 100644 --- a/freedvpkg/README.txt +++ b/freedvpkg/README.txt @@ -1,129 +1 @@ - -Note: The directory freedvpkg no longer contains source files. It only contains copies of - the codec2 libraries for use by Quisk. If any other files are present, delete them. - -FreeDV and Directory freedvpkg -============================== - -FreeDV is the combination of the codec2 codec and the fdmdv modem. It provides digital voice -suitable for HF and VHF transmission. Quisk has native (built-in) support for -FreeDV. Just push the FDV mode and talk. This freedvpkg directory contains copies of the -codec2 libraries for use by Quisk. - -You can also use the separate FreeDV program available at freedv.org, and the Quisk DGT-U mode -which attaches to external digital programs. The setup is identical to fldigi and other external -digital programs. - -Quisk will add the FDV button if specified on the Config/radio/Controls tab. -If there is a problem with the freedv module, the button will be grayed out. - -The freedv module requires the codec2 library. This library is included for Windows and for -Ubuntu 18.04 LTS 32-bit and 64-bit. For other systems (such as ARM) you will need to build another -codec2. Just try the FDV mode and see if it works. It should always work on Windows, and may work -on Linux. If the FDV button is grayed out, you need a different codec2 than the one included. Make -a new codec2 library, and copy it to freedvpkg or to the correct system directory. - -2020 mode -========= -You can now use 2020 mode with Quisk. Note however that 2020 mode requires a processor with AVX. -Also note a Raspberry PI (model 4B) does not have enough processing power even if neon is used -and it is overclocked. - -There are two parts to this: -1. build LPCNet -2. rebuild codec2 -The details are found here: https://github.com/drowe67/codec2#freedv-2020-support-building-with-lpcnet - -Both liblpcnetfreedv and libcodec2 are needed. If you are running under windows, the resultant -liblpcnetfreedv.dll file needs to be placed in the quisk folder. Libcodec2 may be placed in the -quisk freedvpkg folder. Or install the DLL's in the correct system folders. - -Search Order -============ - -Quisk will search for a valid codec2 library in this order on Windows: -1. The system codec2 library installed outside of Quisk by another program. -2. freedvpkg/libcodec2.dll. Not included. You can copy your own codec2 here but a system-wide installation is better. -3. freedvpkg/libcodec2_32.dll. The 32-bit codec2 shipped with Quisk. -4. freedvpkg/libcodec2_64.dll. The 64-bit codec2 shipped with Quisk. - -Quisk will search for a valid codec2 library in this order on Linux: -1. The system codec2 library installed outside of Quisk by another program. -2. freedvpkg/libcodec2.so. Not included. You can copy your own codec2 here but a system-wide installation is better. -3. freedvpkg/libcodec2_32.so. The 32-bit codec2 shipped with Quisk. -4. freedvpkg/libcodec2_64.so. The 64-bit codec2 shipped with Quisk. - -How to Build a New codec2 -========================= - -The source for codec2 is available on github. Or google for other sources or perhaps a pre-built library. -If you need to compile codec2 from source, first change to a suitable directory (not the Quisk directory) -and download the source with git: - - git clone https://github.com/drowe67/codec2.git - -Then build codec2 using the directions found at https://github.com/drowe67/codec2/blob/master/INSTALL. -The directions given below are current as of June 2020, but check for changes. -Then copy the codec2 library to the freedvpkg directory under Quisk. - -Build a New codec2 on Linux -=========================== -Create the codec2 shared library. This assumes a 64-bit linux. Change the directory -name from build_linux64 to build_linux32 for 32-bit linux. Note the "../". - - cd codec2 - mkdir build_linux64 - cd build_linux64 - cmake -DCMAKE_BUILD_TYPE=Release ../ - make - cd src - cp libcodec2.so my-quisk-directory/freedvpkg - -Build a New 32-bit codec2 on 64-bit Linux -========================================= -Make sure package libc6-dev-i386 is installed. -Create the codec2 shared library. Note the "../". - - cd codec2 - mkdir build_linux32 - cd build_linux32 - export CFLAGS=-m32 - cmake -DCMAKE_BUILD_TYPE=Release ../ - make - cd src - cp libcodec2.so my-quisk-directory/freedvpkg - -Build a New codec2 on Windows -============================= -For Windows you need to install MinGW-w64, MSYS2, and g++. Use the mingw shell. The Speex libraries -are not needed by codec2, but are required for the Unit Test modules. To build the Unit Test modules, -you need to install Speex and add -DSPEEXDSP_INCLUDE_DIR=../speex/include/speex -DSPEEXDSP_LIBRARY=../speex/bin. - - # Use mingw32.exe 32-bit shell: - cd codec2 - mkdir build_win32 - cd build_win32 - cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF ../ - make codec2 - cd src - cp libcodec2.dll my-quisk-directory/freedvpkg - - # Use mingw64.exe 64-bit shell: - cd codec2 - mkdir build_win64 - cd build_win64 - cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DUNITTEST=OFF -DCMAKE_SYSTEM_PROCESSOR=x86_64 ../ - make codec2 - cd src - cp libcodec2.dll my-quisk-directory/freedvpkg - -Testing -======= -You can just start Quisk and see if the FDV button is not grayed out, and FDV works. Or you can -test the import of freedv and look for error messages. - - cd my-quisk-directory - python # Use the correct Python 2 or 3 in 32 or 64 bit - import _quisk - _quisk.freedv_get_version() # This should return 13 or higher for a recent codec2 - +This directory is no longer used by FreeDV. Please see the documentation. diff --git a/freedvpkg/libcodec2_64.dll b/freedvpkg/libcodec2_64.dll deleted file mode 100644 index 5c6ee52..0000000 Binary files a/freedvpkg/libcodec2_64.dll and /dev/null differ diff --git a/freedvpkg/libcodec2_64.so b/freedvpkg/libcodec2_64.so deleted file mode 100644 index dd68024..0000000 Binary files a/freedvpkg/libcodec2_64.so and /dev/null differ diff --git a/microphone.c b/microphone.c index 726f8df..cc8a99c 100644 --- a/microphone.c +++ b/microphone.c @@ -8,7 +8,6 @@ #include #include "microphone.h" #include "filter.h" -#include "freedv.h" #ifdef MS_WINDOWS #include @@ -245,11 +244,9 @@ static void init_alc(struct alc * pt, int size) case DGT_U: case DGT_L: case DGT_IQ: - pt->gain_now[i] = 1.4; - break; case FDV_U: case FDV_L: - pt->gain_now[i] = 2.0; + pt->gain_now[i] = 1.4; break; default: pt->gain_now[i] = 1.0; @@ -617,7 +614,7 @@ static int tx_filter_digital(complex double * filtered, int count) quisk_filt_dInit(&filter1, quiskDgtFilt48Coefs, sizeof(quiskDgtFilt48Coefs)/sizeof(double)); // pass 1350, stop 1650 } if ( ! filtered) { // Change to rxMode - quisk_filt_tune(&filter1, 1650.0 / 48000, rxMode != DGT_L && rxMode != LSB); + quisk_filt_tune(&filter1, 1650.0 / 48000, rxMode != DGT_L && rxMode != LSB && rxMode != FDV_L); return 0; } for (i = 0; i < count; i++) // FIR bandpass filter; separate into I and Q @@ -626,140 +623,6 @@ static int tx_filter_digital(complex double * filtered, int count) return count; } -static int tx_filter_freedv(complex double * filtered, int count, int encode) -{ // Input samples are creal(filtered), output is filtered. - // This filter is used for digital voice. - // Changes by Dave Roberts, G8KBB, for additional modes June, 2020. - // Modified by N2ADR January, 2024. - int i; - // The input sample rate is mic_sample_rate, either 8000 or 48000 sps. - // The FreeDV codec requires an input speech_sample_rate of either 8000 or 16000 sps depending on the mode. Input is always real. - // The FreeDV codec will output samples at modem_sample_rate of 8000 or 48000 sps depending on the mode, and output may be real or complex. - double dtmp, magn, dsample; - int modem_sample_rate, speech_sample_rate, use_filter, is_real; - static int samples_size = 0; - static int do_init = 1; - static double aaa, bbb, ccc, Xmin, Xmax, Ymax; - static double time_long, time_short; - static double inMax=0.3; - static double * dsamples = NULL; - static struct quisk_cFilter filter1, filter2; - static struct quisk_dFilter filtDecim48to16, filtDecim16to8;; - static struct quisk_cFilter cfiltInterp8to48; - - if (do_init) { // initialization - //QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16); - do_init = 0; - quisk_filt_cInit(&filter1, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // at 48000 sps pass 3000 stop 4000 times two plus 3000 - quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // at 8000 sps pass 1500 stop 1800 times two plus 1500 - quisk_filt_cInit(&cfiltInterp8to48, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); - quisk_filt_dInit(&filtDecim48to16, quiskAudio24p3Coefs, sizeof(quiskAudio24p3Coefs)/sizeof(double)); // pass 6000 stop 8000 - quisk_filt_dInit(&filtDecim16to8, quiskFilt16dec8Coefs, sizeof(quiskFilt16dec8Coefs)/sizeof(double)); // pass 3000 stop 4000 - Ymax = pow(10.0, - 1 / 20.0); // maximum y - Xmax = pow(10.0, 3 / 20.0); // x where slope is zero; for x > Xmax, y == Ymax - Xmin = Ymax - fabs(Ymax - Xmax); // x where slope is 1 and y = x; start of compression - //printf ("Xmin %f\n", Xmin); - aaa = 1.0 / (2.0 * (Xmin - Xmax)); // quadratic - bbb = -2.0 * aaa * Xmax; - ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax; - } - switch (freedv_current_mode) { // n_modem_sample_rate and n_speech_sample_rate may not be valid - case FREEDV_MODE_2400A: - case FREEDV_MODE_2400B: - use_filter = 1; // center frequency 3000 - is_real = 1; - modem_sample_rate = 48000; - speech_sample_rate = 8000; - break; - case FREEDV_MODE_2020: - use_filter = 0; - is_real = 0; - modem_sample_rate = 8000; - speech_sample_rate = 16000; - break; - case FREEDV_MODE_800XA: - use_filter = 2; // center frequency 1500 - is_real = 1; - modem_sample_rate = 8000; - speech_sample_rate = 8000; - break; - default: - use_filter = 0; - is_real = 0; - modem_sample_rate = 8000; - speech_sample_rate = 8000; - break; - } - if ( ! filtered) { // rxMode changed - quisk_filt_tune((struct quisk_dFilter *)&filter1, 3000.0 / 48000.0, rxMode != FDV_L); - quisk_filt_tune((struct quisk_dFilter *)&filter2, 1500.0 / 8000.0, rxMode != FDV_L); - return 0; - } - // check size of dsamples[] buffer - if (count > samples_size) { - samples_size = count * 2; - if (dsamples) - free(dsamples); - dsamples = (double *)malloc(samples_size * sizeof(double)); - } - // copy to dsamples[] - for (i = 0; i < count; i++) - dsamples[i] = creal(filtered[i]) / CLIP16; // normalize to 1.0000 - // Decimate to 8000 Hz or to 16000 depending on mode (and hence setting of sample rate) - if (quisk_sound_state.mic_sample_rate == 48000) { - switch (speech_sample_rate) { - case 16000: - count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3); - break; - case 8000: - count = quisk_dDecimate(dsamples, count, &filtDecim48to16, 3); - count = quisk_dDecimate(dsamples, count, &filtDecim16to8, 2); - break; - default: - QuiskPrintf("Failure to convert speech input rate in tx_filter_freedv\n"); - break; - } - } - else if (quisk_sound_state.mic_sample_rate == 8000 && speech_sample_rate != 8000) { - QuiskPrintf("Failure to convert input rate in tx_filter_freedv\n"); - } - // Measure average peak input audio level and limit - dtmp = 1.0 / speech_sample_rate; // sample time - time_long = 1.0 - exp(- dtmp / 3.000); - time_short = 1.0 - exp(- dtmp / 0.005); - for (i = 0; i < count; i++) { - dsample = dsamples[i]; - magn = fabs(dsample); - if (magn > inMax) - inMax = inMax * (1 - time_short) + time_short * magn; - else if(magn > mic_agc_level) - inMax = inMax * (1 - time_long) + time_long * magn; - else - inMax = inMax * (1 - time_long) + time_long * mic_agc_level; - dsample = dsample / inMax * Xmin * 0.7; - magn = fabs(dsample); - if (magn < Xmin) - dsamples[i] = dsample; - else if (magn > Xmax) - dsamples[i] = copysign(Ymax, dsample); - else - dsamples[i] = copysign(aaa * magn * magn + bbb * magn + ccc, dsample); - dsamples[i] = dsamples[i] * CLIP16; - } - //QuiskWavWriteD(&hWav, dsamples, count); - if (encode && pt_quisk_freedv_tx) // Encode audio into digital modulation - count = (* pt_quisk_freedv_tx)(filtered, dsamples, count, is_real); - //quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0); - if (use_filter == 1) // convert float samples to complex with an analytic filter - count = quisk_cCDecimate(filtered, count, &filter1, 1); - else if (use_filter == 2) - count = quisk_cCDecimate(filtered, count, &filter2, 1); - // Interpolate up to 48000 - if (MIC_OUT_RATE != modem_sample_rate ) - count = quisk_cInterpolate(filtered, count, &cfiltInterp8to48, MIC_OUT_RATE / modem_sample_rate); - return count; -} - PyObject * quisk_get_tx_filter(PyObject * self, PyObject * args) { // return the TX filter response to display on the graph // This is for debugging. Change quisk.py to call QS.get_tx_filter() instead @@ -1397,6 +1260,8 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int case DGT_L: case DGT_IQ: case DGT_FM: + case FDV_U: // FreeDV + case FDV_L: count = tx_filter_digital(cSamples, count); // filter samples, minimal processing process_alc(cSamples, count, &tx_alc, rxMode); break; @@ -1404,11 +1269,6 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int count *= interp; transmit_mic_imd(cSamples, count, quiskImdLevel / 1000.0); break; - case FDV_U: // FDV - case FDV_L: - count = tx_filter_freedv(cSamples, count, 1); - process_alc(cSamples, count, &tx_alc, rxMode); - break; default: break; } @@ -1499,11 +1359,6 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int count = tx_filter(cSamples, count); process_alc(cSamples, count, &tx_alc, rxMode); break; - case FDV_U: // FDV - case FDV_L: - count = tx_filter_freedv(cSamples, count, 0); - process_alc(cSamples, count, &tx_alc, rxMode); - break; default: for (i = 0; i < count; i++) cSamples[i] = 0; @@ -1673,7 +1528,6 @@ void quisk_set_tx_mode(void) // called when the mode rxMode is changed tx_filter(NULL, 0); tx_filter_digital(NULL, 0); transmit_udp(NULL, 0); - tx_filter_freedv(NULL, 0, 0); serial_key_samples(NULL, 0); } diff --git a/quisk.c b/quisk.c index 781de63..c41709d 100644 --- a/quisk.c +++ b/quisk.c @@ -151,7 +151,6 @@ static int waterfall_scroll_mode = 1; // draw the first lines multiple times static int quisk_use_sidetone; // is there a sidetone volume control? static int hl2_txbuf_errors; // errors in the Hermes-Lite2 Tx buffer static int hl2_txbuf_state; // state machine for errors in the Hermes-Lite2 Tx buffer -static int freedv_monitor; // pass the Freedv audio to the speakers instead of to the demodulator static int softrock_correct_freq; // gain and phase corrections for SoftRock static double softrock_correct_gain; static double softrock_correct_phase; @@ -569,15 +568,10 @@ void QuiskWavClose(struct QuiskWav * hWav) } #endif -// These are used for digital voice codecs -ty_dvoice_codec_rx pt_quisk_freedv_rx; -ty_dvoice_codec_tx pt_quisk_freedv_tx; - -void quisk_dvoice_freedv(ty_dvoice_codec_rx rx, ty_dvoice_codec_tx tx) +void quisk_dvoice_freedv(void) // Not used. { - pt_quisk_freedv_rx = rx; - pt_quisk_freedv_tx = tx; } + #if 0 static int fFracDecim(double * dSamples, int nSamples, double fdecim) { // fractional decimation by fdecim > 1.0 @@ -1851,10 +1845,8 @@ static int quisk_process_decimate(complex double * cSamples, int nSamples, int b static int quisk_process_demodulate(complex double * cSamples, double * dsamples, int nSamples, int bank, int nFilter, rx_mode_type rx_mode) { // Changes here will require changes to get_filter_rate(); int i; - bool isLSB; complex double cx; double d, di, dd; - static struct AgcState Agc1 = {0.3, 16000, 0}, Agc2 = {0.3, 16000, 0}; //static int count=0; //static double phase=0; static struct stStorage { @@ -2090,6 +2082,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples MeasureSquelch[bank].squelch_active = MeasureSquelch[bank].squelch < squelch_level; break; case DGT_U: // digital mode DGT-U at 48 ksps + case FDV_U: if (filter_bandwidth[nFilter] < DGT_NARROW_FREQ) { // filter at 6 ksps quisk_filter_srate = quisk_decim_srate / 8; nSamples = quisk_cDecim2HB45(cSamples, nSamples, &Storage[bank].HalfBand5); @@ -2116,6 +2109,7 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples } break; case DGT_L: // digital mode DGT-L + case FDV_L: if (filter_bandwidth[nFilter] < DGT_NARROW_FREQ) { // filter at 6 ksps quisk_filter_srate = quisk_decim_srate / 8; nSamples = quisk_cDecim2HB45(cSamples, nSamples, &Storage[bank].HalfBand5); @@ -2154,102 +2148,6 @@ static int quisk_process_demodulate(complex double * cSamples, double * dsamples } } break; - case FDV_U: // digital voice - case FDV_L: // Extra modes added by Dave Roberts, G8KBB, June 2020. Modified by N2ADR 2024. - quisk_check_freedv_mode(); - // current coding assumes input rate n_modem_sample_rate is 8000 or 48000. - // decimate: - switch (n_modem_sample_rate) { - case 8000: - quisk_filter_srate = quisk_decim_srate / 6; - nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim48to16, 3); - nSamples = quisk_cDecimate(cSamples, nSamples, &Storage[bank].filtDecim16to8, 2); - break; - case 48000: - quisk_filter_srate = quisk_decim_srate; - break; - default: - quisk_filter_srate = quisk_decim_srate / 6; - QuiskPrintf("Bad FreeDV modem rate in quisk_process_demodulate\n"); - break; - } - // filter and demodulate to mono audio: - isLSB = (rx_mode == FDV_L); - complex double cBuf[nSamples]; - for (i = 0; i < nSamples; i++) { - cx = cRxFilterOut(cSamples[i], bank, nFilter); - if (isLSB) - dd = creal(cx) + cimag(cx); - else - dd = creal(cx) - cimag(cx); - if(bank == 0) { - measure_audio_sum += dd * dd; - measure_audio_count += 1; - } - cBuf[i] = dd; - } - // Perhaps decimate by an additional fraction - if (quisk_decim_srate != 48000) { - dd = quisk_decim_srate / 48000.0; - nSamples = cFracDecim(cBuf, nSamples, dd); - quisk_decim_srate = 48000; - } - if (bank == 0) - process_agc(&Agc1, cBuf, nSamples, 0); - else - process_agc(&Agc2, cBuf, nSamples, 0); - // pass data decimated down to n_modem_sample_rate - // return data is at n_speech_sample_rate -#if 0 - static double dmax_in = 0; - for (i = 0; i < nSamples; i++) { - dd = fabs(creal(cBuf[i])) / CLIP16; - if (dd > dmax_in) - dmax_in = dd; - } - if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) { - //printf("Max sample in %.0lf\n", dmax_in); - dmax_in = 0; - } -#endif - if (nSamples > n_max_modem_samples) { - printf ("FreeDV: nSamples %d exceeds max_modem_samples %d\n", nSamples, n_max_modem_samples); - nSamples = n_max_modem_samples; - } - short sBuf[nSamples]; - for (i = 0; i < nSamples; i++) - sBuf[i] = (short)(creal(cBuf[i]) / CLIP16); - if (freedv_monitor) { - for (i = 0; i < nSamples; i++) - dsamples[i] = (float)sBuf[i] * CLIP16; - } - else { - if (pt_quisk_freedv_rx) - nSamples = (* pt_quisk_freedv_rx)(sBuf, dsamples, nSamples, bank); - } -#if 0 - static double dmax_out = 0; - for (i = 0; i < nSamples; i++) { - dd = fabs(dsamples[i]) / CLIP16; - if (dd > dmax_out) - dmax_out = dd; - } - if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) { - //printf(" Max sample out %.0lf\n", dmax_out); - dmax_out = 0; - } -#endif - // current coding assumes output rate n_speech_sample_rate is 8000 or 16000 - switch (n_speech_sample_rate) { - case 8000: - nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3); - nSamples = quisk_dInterp2HB45(dsamples, nSamples, &Storage[bank].HalfBand7); - break; - case 16000: - nSamples = quisk_dInterpolate(dsamples, nSamples, &Storage[bank].filtAudio24p3, 3); - break; - } - break; } if (bank == 0 && measure_audio_count >= quisk_filter_srate * measure_audio_time) { measured_audio = sqrt(measure_audio_sum / measure_audio_count) / CLIP32 * 1e6; @@ -2785,9 +2683,6 @@ start_agc: if (rxMode == EXT || rxMode == DGT_IQ) { // Ext and DGT-IQ stereo sound process_agc(&Agc1, cSamples, nSamples, 1); } - else if (rxMode == FDV_U || rxMode == FDV_L) { // Agc already done - ; - } else if (split_rxtx || multirx_play_channel >= 0) { // separate AGC for left and right channels for (i = 0; i < nSamples; i++) { orig_cSamples[i] = cimag(cSamples[i]); @@ -2945,6 +2840,8 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args) break; case DGT_U: // digital modes DGT-* case DGT_L: + case FDV_U: // digital voice + case FDV_L: if (bandwidth < DGT_NARROW_FREQ) filter_srate = decim_srate / 8; else @@ -2953,10 +2850,6 @@ static PyObject * get_filter_rate(PyObject * self, PyObject * args) case DGT_IQ: // digital mode at 48 ksps filter_srate = decim_srate; break; - case FDV_U: // digital voice - case FDV_L: - filter_srate = n_modem_sample_rate; - break; } //QuiskPrintf("Filter rate %d\n", filter_srate); return PyInt_FromLong(filter_srate); @@ -3196,14 +3089,13 @@ static PyObject * set_params(PyObject * self, PyObject * args, PyObject * keywds { /* Call with keyword arguments ONLY; change local parameters */ static char * kwlist[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip", "bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause", - "freedv_monitor", NULL} ; + NULL} ; int i, nbytes, read_error, clip, bscope_size, hermes_pause; nbytes = read_error = clip = bscope_size = hermes_pause = -1; if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidii", kwlist, &quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip, - &py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause, - &freedv_monitor)) + &py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause)) return NULL; if (nbytes != -1) { py_sample_rx_bytes = nbytes; @@ -6269,13 +6161,6 @@ static PyMethodDef QuiskMethods[] = { {"set_cwkey", set_hardware_cwkey, METH_VARARGS, "Change the CW key up/down state."}, {"set_remote_cwkey", set_remote_cwkey, METH_VARARGS, "Change the remote control CW key up/down state."}, {"set_PTT", set_PTT, METH_VARARGS, "Record the PTT button state."}, - {"freedv_open", quisk_freedv_open, METH_VARARGS, "Open FreeDV."}, - {"freedv_close", quisk_freedv_close, METH_VARARGS, "Close FreeDV."}, - {"freedv_get_snr", quisk_freedv_get_snr, METH_VARARGS, "Return the signal to noise ratio in dB."}, - {"freedv_get_version", quisk_freedv_get_version, METH_VARARGS, "Return the codec2 API version."}, - {"freedv_get_rx_char", quisk_freedv_get_rx_char, METH_VARARGS, "Get text characters received from freedv."}, - {"freedv_set_options", (PyCFunction)quisk_freedv_set_options, METH_VARARGS|METH_KEYWORDS, "Set the freedv parameters."}, - {"freedv_set_squelch_en", quisk_freedv_set_squelch_en, METH_VARARGS, "Enable or disable FreeDV squelch."}, {"wdsp_set_parameter", (PyCFunction)quisk_wdsp_set_parameter, METH_VARARGS|METH_KEYWORDS, "Set parameters for the WDSP SDR library."}, {"tmp_record_save", tmp_record_save, METH_VARARGS, "Save the temporary recording in a WAV file."}, {"watfall_RgbData", watfall_RgbData, METH_VARARGS, "Return a cookie for the Waterfall pixel data."}, diff --git a/quisk.h b/quisk.h index 696296b..d22f73f 100644 --- a/quisk.h +++ b/quisk.h @@ -312,10 +312,6 @@ extern int quisk_active_sidetone; // Whether and how to generate a sidetone extern int quisk_isFDX; // Are we in full duplex mode? extern int quisk_use_serial_port; // Are we using the serial port for CW key or PTT? extern play_state_t quisk_play_state; // startup, receiving, sidetone -extern int freedv_current_mode; // current FreeDV mode; 700D, @)@) etc. -extern int n_modem_sample_rate; // Receive data, decimate to modem_sample_rate, FreeDV codec output data at speech_sample_rate -extern int n_speech_sample_rate; // Microphone decimate to speech_sample_rate, Freedv codec output data at modem_sample_rate, interpolate to 48000 -extern int n_max_modem_samples; // maximum input to freedv_rx() extern int quisk_start_cw_delay; // milliseconds to delay output on serial or MIDI CW key down extern int quisk_start_ssb_delay; // milliseconds to discard output for all modes except CW extern struct sound_dev * quiskPlaybackDevices[]; // array of Playback sound devices @@ -346,14 +342,7 @@ extern PyObject * quisk_set_hermes_filter(PyObject *, PyObject *); extern PyObject * quisk_set_alex_hpf(PyObject *, PyObject *); extern PyObject * quisk_set_alex_lpf(PyObject *, PyObject *); -extern PyObject * quisk_freedv_open(PyObject *, PyObject *); -extern PyObject * quisk_freedv_close(PyObject *, PyObject *); -extern PyObject * quisk_freedv_get_snr(PyObject *, PyObject *); -extern PyObject * quisk_freedv_get_version(PyObject *, PyObject *); -extern PyObject * quisk_freedv_get_rx_char(PyObject *, PyObject *); -extern PyObject * quisk_freedv_set_options(PyObject *, PyObject *, PyObject *); extern PyObject * quisk_set_sparams(PyObject *, PyObject *, PyObject *); -extern PyObject * quisk_freedv_set_squelch_en(PyObject *, PyObject *); extern PyObject * quisk_open_key(PyObject *, PyObject *, PyObject *); extern PyObject * quisk_close_key(PyObject *, PyObject *); extern PyObject * quisk_set_sound_name(PyObject *, PyObject *); @@ -397,7 +386,6 @@ void quisk_file_playback(complex double *, int, double); void quisk_tmp_playback(complex double *, int, double); void quisk_hermes_tx_send(int, int *); void quisk_udp_mic_error(char *); -void quisk_check_freedv_mode(void); void quisk_calc_audio_graph(double, complex double *, double *, int, int); double QuiskDeltaSec(int); void * quisk_make_sidetone(struct sound_dev *, int); @@ -407,12 +395,6 @@ void quisk_set_play_state(void); void quisk_poll_hardware_key(void); void PreDistort(complex double * amp_in_samples, complex double * amp_out_samples, int nSamples, complex double * tx_samples, int num_tx); -// Functions supporting digital voice codecs -typedef int (* ty_dvoice_codec_rx)(short *, double *, int, int); -typedef int (* ty_dvoice_codec_tx)(complex double *, double *, int, int); -extern ty_dvoice_codec_rx pt_quisk_freedv_rx; -extern ty_dvoice_codec_tx pt_quisk_freedv_tx; - // Driver function definitions================================================= int quisk_read_alsa(struct sound_dev *, complex double *); void quisk_play_alsa(struct sound_dev *, int, complex double *, int, double); @@ -468,7 +450,7 @@ int import_quisk_api(void); // used to initialize Quisk_API #define QuiskSleepMicrosec (*( void (*) (int) )Quisk_API[5]) #define QuiskPrintTime (*( void (*) (const char *, int) )Quisk_API[6]) #define quisk_sample_source (*( void (*) (ty_sample_start, ty_sample_stop, ty_sample_read) )Quisk_API[7]) -#define quisk_dvoice_freedv (*( void (*) (ty_dvoice_codec_rx, ty_dvoice_codec_tx) )Quisk_API[8]) +#define quisk_dvoice_freedv (*( void (*) (void) )Quisk_API[8]) #define quisk_is_key_down (*( int (*) (void) )Quisk_API[9]) #define quisk_sample_source4 (*( void (*) (ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write) )Quisk_API[10]) #define strMcpy (*( char * (*) (char *, const char *, size_t) )Quisk_API[11]) @@ -483,7 +465,7 @@ double QuiskTimeSec(void); void QuiskSleepMicrosec(int); void QuiskPrintTime(const char *, int); void quisk_sample_source(ty_sample_start, ty_sample_stop, ty_sample_read); -void quisk_dvoice_freedv(ty_dvoice_codec_rx, ty_dvoice_codec_tx); +void quisk_dvoice_freedv(void); int quisk_is_key_down(void); void quisk_sample_source4(ty_sample_start, ty_sample_stop, ty_sample_read, ty_sample_write); char * strMcpy(char *, const char *, size_t); diff --git a/quisk.py b/quisk.py index 8bfe03c..a0c6b68 100644 --- a/quisk.py +++ b/quisk.py @@ -292,9 +292,9 @@ class HamlibHandlerSerial: # Additional logic contributed by Dr. Karsten Schmidt - Mo2CoKen = {'CWL':7, 'CWU':3, 'LSB':1, 'USB':2, 'AM':5, 'FM':4, 'DGT-U':9, 'DGT-L':6, 'DGT-FM':4, 'DGT-IQ':9} + Mo2CoKen = {'CWL':7, 'CWU':3, 'LSB':1, 'USB':2, 'AM':5, 'FM':4, 'DGT-U':9, 'FDV-U':9, 'DGT-L':6, 'FDV-L':6, 'DGT-FM':4, 'DGT-IQ':9} Co2MoKen = {1:'LSB', 2:'USB', 3:'CWU', 4:'FM', 5:'AM', 6:'DGT-L', 7:'CWL', 9:'DGT-U'} - Mo2CoFlex = {'CWL':3, 'CWU':4, 'LSB':0, 'USB':1, 'AM':6, 'FM':5, 'DGT-U':7, 'DGT-L':9, 'DGT-FM':5, 'DGT-IQ':7} + Mo2CoFlex = {'CWL':3, 'CWU':4, 'LSB':0, 'USB':1, 'AM':6, 'FM':5, 'DGT-U':7, 'FDV-U':7, 'DGT-L':9, 'FDV-L':9, 'DGT-FM':5, 'DGT-IQ':7} Co2MoFlex = {0:'LSB', 1:'USB', 3:'CWL', 4:'CWU', 5:'FM', 6:'AM', 7:'DGT-U', 9:'DGT-L'} ConvertZZAC_P1_2_Step = { 0: 1, 1: 10, @@ -1061,9 +1061,9 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532 mode = 'CW' elif mode == 'CWL': # Is this what CWR means? mode = 'CWR' - elif mode == 'DGT-U': + elif mode in ('DGT-U', 'FDV-U'): mode = 'PKTUSB' - elif mode == 'DGT-L': + elif mode in ('DGT-L', 'FDV-L'): mode = 'PKTLSB' elif mode == 'DGT-FM': mode = 'PKTFM' @@ -3727,7 +3727,7 @@ class App(wx.App): 'txAudioPreemphUsb', 'txAudioPreemphAm', 'txAudioPreemphFm', 'txAudioPreemphFdv', 'wfallScaleZ', 'wfallGrScaleZ', 'graphScaleZ', 'split_rxtx_play', 'modeFilter', 'file_name_rec_audio', 'file_name_rec_samples', 'file_name_rec_mic', 'file_name_rec_tmp', - 'file_name_play_audio', 'file_name_play_samples', 'file_name_play_cq', 'freedv_mode', + 'file_name_play_audio', 'file_name_play_samples', 'file_name_play_cq', 'file_play_source', 'hermes_LNA_dB'] def __init__(self): global application @@ -3948,8 +3948,6 @@ class App(wx.App): self.hot_key_ptt_active = False self.serial_ptt_active = False self.vox_ptt_active = False - self.freedv_mode = 'Mode 700D' # restore FreeDV mode setting - self.freedv_menu = None self.hermes_LNA_dB = 20 if hasattr(Hardware, "OnChangeRxTx"): self.want_RxTx = True @@ -4711,8 +4709,6 @@ class App(wx.App): self.hamlib_com1_handler.close() if self.hamlib_com2_handler: self.hamlib_com2_handler.close() - if conf.add_freedv_button: - QS.freedv_close() msg = QS.GetQuiskPrintf() if msg: print(msg, end='') @@ -5051,14 +5047,9 @@ class App(wx.App): self.config_screen.config.EnableRecPlay() ### Right bank of buttons mode_names = ['CWL', 'CWU', 'LSB', 'USB', 'AM', 'FM', 'DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ', 'FDV-U', 'FDV-L', 'IMD'] - labels = [('CWL', 'CWU'), ('LSB', 'USB'), 'AM', 'FM', ('DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ')] - shortcuts = ['C', 'S', 'A', 'M', 'D'] - count = 5 # There is room for seven buttons - if conf.add_freedv_button: - n_freedv = count - count += 1 - labels.append('FDV-U') - shortcuts.append('F') + labels = [('CWL', 'CWU'), ('LSB', 'USB'), 'AM', 'FM', ('DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ'), ('FDV-U', 'FDV-L')] + shortcuts = ['C', 'S', 'A', 'M', 'D', 'F'] + count = 6 # There is room for seven buttons if conf.add_imd_button: n_imd = count count += 1 @@ -5080,43 +5071,11 @@ class App(wx.App): self.modeButns.buttons[0].idName = "CW U/L" self.modeButns.buttons[1].idName = "SSB U/L" self.modeButns.buttons[4].idName = "DGT" + self.modeButns.buttons[5].idName = "FDV" else: labels = ['CWL', 'CWU', 'LSB', 'USB', 'AM', 'FM', 'DGT-U', 'DGT-L', 'DGT-FM', 'DGT-IQ', 'FDV-U', 'IMD'] self.modeButns = RadioBtnPopup(frame, self.OnBtnMode, labels, None, 'modeButns') self.modeButns.idName = "modeButns" - self.freedv_menu_items = {} - if conf.add_freedv_button: - self.freedv_menu = QuiskMenu("freedv_menu") - msg = conf.freedv_tx_msg - QS.freedv_set_options(mode=conf.freedv_modes[0][1], tx_msg=msg, DEBUG=0, squelch=1) - self.freedv_menu.AppendCheckItem("Monitor", self.OnFreedvMonitor) - self.freedv_menu.AppendSeparator() - for mode, index in conf.freedv_modes: - item = self.freedv_menu.AppendRadioItem(mode, self.OnFreedvMenu, mode == self.freedv_mode) - self.freedv_menu_items[index] = item - if mode == self.freedv_mode: # Restore mode - QS.freedv_set_options(mode=index) - if mode in ("Mode 2020", "Mode 2400A", "Mode 2400B"): - item.Enable(False) - if conf.button_layout == 'Large screen': - b = QuiskCycleCheckbutton(frame, None, ('FDV-U', 'FDV-L'), is_radio=True) - b.idName = "FDV" - b.char_shortcut = 'F' - self.btnFreeDV = WrapMenu(b, self.freedv_menu) - self.modeButns.ReplaceButton(n_freedv, self.btnFreeDV) - else: - self.btnFreeDV = self.modeButns.AddMenu('FDV-U', self.freedv_menu) - try: - ok = QS.freedv_open() - except: - traceback.print_exc() - ok = 0 - if not ok: - conf.add_freedv_button = False - if conf.button_layout == 'Large screen': - self.modeButns.GetButtons()[n_freedv].Enable(0) - else: - self.modeButns.Enable('FDV-U', False) if conf.add_imd_button: val = 1000 QS.set_imd_level(val) @@ -5395,16 +5354,6 @@ class App(wx.App): t = '%13.2f' % (QS.measure_frequency(-1) + vfo) t = t[0:4] + ' ' + t[4:7] + ' ' + t[7:] + ' Hz' self.smeter.SetLabel(t) - def NewDVmeter(self): - if conf.add_freedv_button: - snr = QS.freedv_get_snr() - txt = QS.freedv_get_rx_char() - self.graph.ScrollMsg(txt) - self.waterfall.ScrollMsg(txt) - else: - snr = 0.0 - t = " SNR %3.0f" % snr - self.smeter.SetLabel(t) def NewSmeter(self): self.smeter_db_count += 1 # count for average x = QS.get_smeter() @@ -5574,66 +5523,6 @@ class App(wx.App): self.multi_rx_screen.waterfall.pane2.filter_center = center if self.screen is self.filter_screen: self.screen.NewFilter() - def OnFreedvMonitor(self, event): - for item in self.freedv_menu.GetMenuItems(): - if item.GetKind() == wx.ITEM_CHECK: - if item.IsChecked(): - QS.set_params(freedv_monitor=1) - else: - QS.set_params(freedv_monitor=0) - break - def OnFreedvMenu(self, event): - text = '' - for item in self.freedv_menu.GetMenuItems(): - kind = item.GetKind() - if kind != wx.ITEM_RADIO: - continue - if item.IsChecked(): - text = item.GetItemLabel() - break - for mode, index in conf.freedv_modes: - if mode == text: - break - else: - print ("Failure in OnFreedvMenu") - return - mode = QS.freedv_set_options(mode=index) - if mode == index: - self.freedv_mode = text - elif not self.remote_control_slave: # change to new mode failed - self.freedv_menu_items[mode].Check(1) - pos = (self.width//2, self.height//2) - if index == 8: - dlg = wx.MessageDialog(self.main_frame, "Quisk does not install %s. Please install a system-wide codec2." % text, "FreeDV Modes", wx.OK, pos) - else: - dlg = wx.MessageDialog(self.main_frame, "No codec2 support for " + text, "FreeDV Modes", wx.OK, pos) - dlg.ShowModal() - else: - self.freedv_menu_items[mode].Check(1) - print ("FreeDV change mode failed.") - self.OnChangeFilter() - def OnChangeFilter(self): - # Used by OnFreedvMenu because the FreeDV mode may change the filter sample rate - bw = int(self.filterButns.GetLabel()) - # We can not use QS.get_filter_srate() because the FreeDV mode is not changed yet. - if self.freedv_mode in ("Mode 2400A", "Mode 2400B"): - frate = 48000; - else: - frate = 8000; - bw = min(bw, frate // 2) - self.filter_bandwidth = bw - center = self.GetFilterCenter(self.mode, bw) - filtI, filtQ = self.MakeFilterCoef(frate, None, bw, center) - lower_edge = center - bw // 2 - QS.set_filters(filtI, filtQ, bw, lower_edge, 0) - self.multi_rx_screen.graph.filter_bandwidth = bw - self.multi_rx_screen.graph.filter_center = center - self.multi_rx_screen.waterfall.pane1.filter_bandwidth = bw - self.multi_rx_screen.waterfall.pane1.filter_center = center - self.multi_rx_screen.waterfall.pane2.filter_bandwidth = bw - self.multi_rx_screen.waterfall.pane2.filter_center = center - if self.screen is self.filter_screen: - self.screen.NewFilter() def OnBtnHelp(self, event): if event.GetEventObject().GetValue(): self.OnBtnScreen(None, 'Help') @@ -6098,11 +5987,6 @@ class App(wx.App): QS.set_squelch(self.levelSquelch / 12.0 - 120.0) else: QS.set_squelch(-999.0) - elif self.mode[0:3] == 'FDV': - if value: - QS.freedv_set_squelch_en(1) - else: - QS.freedv_set_squelch_en(0) else: self.levelSquelchSSB = btn.slider_value if value: @@ -6896,10 +6780,7 @@ class App(wx.App): d = 1E-10 self.smeter.SetLabel(" ADC %.0f%% %.0fdB" % (d * 100.0, 20 * math.log10(d))) elif self.smeter_usage == "smeter": # update the S-meter - if self.mode in ('FDV-U', 'FDV-L'): - self.NewDVmeter() - else: - self.NewSmeter() + self.NewSmeter() elif self.smeter_usage == "freq": self.MeasureFrequency() # display measured frequency else: diff --git a/quisk_conf_defaults.py b/quisk_conf_defaults.py index 3e9b949..67d7086 100644 --- a/quisk_conf_defaults.py +++ b/quisk_conf_defaults.py @@ -1797,23 +1797,6 @@ add_extern_demod = "" add_fdx_button = 0 #add_fdx_button = 1 -## add_freedv_button Add FreeDv button, integer choice -# These buttons add up to two additional mode buttons after CW, USB, etc. -# Set this to add the FDV mode button for digital voice: -add_freedv_button = 1 -#add_freedv_button = 0 - -## freedv_tx_msg FreeDv Tx message, text -# For freedv, this is the text message to send. -freedv_tx_msg = '' -#freedv_tx_msg = 'N2XXX Jim, New Jersey, USA \n' - - -# This is the list of FreeDV modes and their index number. The starting mode is the first listed. -freedv_modes = (('Mode 1600', 0), - ('Mode 2400A', 3), ('Mode 2400B', 4), ('Mode 800XA', 5), - ('Mode 700C', 6), ('Mode 700D', 7), ('Mode 700E', 13), ('Mode 2020', 8)) - # These are the filter bandwidths for each mode. Quisk has built-in optimized filters # for these values, but you can change them if you want. FilterBwCW = (200, 400, 600, 1000, 1500, 3000) diff --git a/setup.py b/setup.py index 4fb20cd..b7bbc16 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ import os # You must define the version here. A title string including # the version will be written to __init__.py and read by quisk.py. -Version = '4.2.44' +Version = '4.2.45' fp = open("__init__.py", "w") # write title string fp.write("#Quisk version %s\n" % Version) diff --git a/sound.c b/sound.c index c2bce46..aab2415 100644 --- a/sound.c +++ b/sound.c @@ -1000,21 +1000,25 @@ int quisk_read_sound(void) // Called from sound thread if (remote_control_slave) send_remote_radio_sound_socket(cSamples, nSamples); - is_DGT = rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM; + is_DGT = (rxMode == DGT_U || rxMode == DGT_L || rxMode == DGT_IQ || rxMode == DGT_FM || rxMode == FDV_U || rxMode == FDV_L); if (quisk_record_state == TMP_PLAY_SPKR_MIC) quisk_tmp_playback(cSamples, nSamples, 1.0); // replace radio sound else if (quisk_record_state == FILE_PLAY_SPKR_MIC) quisk_file_playback(cSamples, nSamples, file_play_level); // replace radio sound // Play the demodulated audio #if DEBUG_MIC != 2 - if ( ! quisk_play_sidetone(&quisk_Playback)) // play sidetone - play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // else play radio sound + if ( ! quisk_play_sidetone(&quisk_Playback)) { // play sidetone + if (rxMode == FDV_U || rxMode == FDV_L) + play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, 0); // play silence + else + play_sound_interface(&quisk_Playback, nSamples, cSamples, 1, quisk_audioVolume); // play radio sound + } #endif if (radio_sound_socket != INVALID_SOCKET) send_radio_sound_socket(cSamples, nSamples, quisk_audioVolume); // Play digital if required - if (is_DGT) + //if (is_DGT) play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level); // Perhaps record the speaker audio to a file diff --git a/sound_pulseaudio.c b/sound_pulseaudio.c index 66bd672..6b38c00 100644 --- a/sound_pulseaudio.c +++ b/sound_pulseaudio.c @@ -64,12 +64,20 @@ volatile int streams_to_start; // remember all open devices for easy cleanup on exit static pa_stream *OpenPulseDevices[PA_LIST_SIZE * 2] = {NULL}; -static int have_QuiskDigitalInput; // Do we need to create this null sink? -static int have_QuiskDigitalOutput; // Do we need to create this null sink? - static int underrunPlayback; static int tlength_bytes; +// list of additional sink devices to add +struct _NewSinks { + char * name; // name of the new sink + int create; // create the sink if it doesn't exist +}; +#define NUM_NEW_SINKS 2 +static struct _NewSinks sink_list[NUM_NEW_SINKS] = { + {"QuiskDigitalInput", 1}, + {"QuiskDigitalOutput", 1} +}; + /* This callback happens any time a stream changes state. Here, it's primary used to * tell the quisk thread when streams are ready. */ @@ -1043,19 +1051,23 @@ static void source_sink(const char * name, const char * descr, pa_proplist * pro // pa_mainloop will call this function when it's ready to tell us about a sink. static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { - if (eol > 0) // If eol is set to a positive number, you're at the end of the list + int i; + + if (eol > 0) // If eol is set to a positive number, you're at the end of the list return; - source_sink(l->name, l->description, l->proplist, (PyObject *)userdata); - if ( ! strcmp(l->name, "QuiskDigitalInput")) - have_QuiskDigitalInput = 1; - if ( ! strcmp(l->name, "QuiskDigitalOutput")) - have_QuiskDigitalOutput = 1; + source_sink(l->name, l->description, l->proplist, (PyObject *)userdata); + for (i = 0; i < NUM_NEW_SINKS; i++) { + if ( ! strcmp(l->name, sink_list[i].name)) { + sink_list[i].create = 0; + } + } } -static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { - if (eol > 0) +static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) +{ + if (eol > 0) return; - source_sink(l->name, l->description, l->proplist, (PyObject *)userdata); + source_sink(l->name, l->description, l->proplist, (PyObject *)userdata); } static void index_callback(pa_context *c, uint32_t idx, void *userdata) { @@ -1070,6 +1082,9 @@ PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args) pa_operation *pa_op=NULL; pa_context *pa_names_ctx; int state = 0; + int sink_index = 0; + char * sink_name; + char buf256[256]; if (!PyArg_ParseTuple (args, "")) return NULL; @@ -1113,59 +1128,41 @@ PyObject * quisk_pulseaudio_sound_devices(PyObject * self, PyObject * args) pa_mainloop_iterate(pa_names_ml, 1, NULL); break; case 2: - // Now we wait for our operation to complete. When it's - // complete our pa_output_devicelist is filled out, and we move - // along to the next state if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { pa_operation_unref(pa_op); - // Now we perform another operation to get the source - // (input device) list just like before. - pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt); - // Update the state so we know what to do next state++; } - pa_mainloop_iterate(pa_names_ml, 1, NULL); + else + pa_mainloop_iterate(pa_names_ml, 1, NULL); break; + // The following loads null sinks for digital input and output if they are not present. case 3: + if (sink_index >= NUM_NEW_SINKS) { + state = 5; + continue; + } + if (sink_list[sink_index].create) { + sink_name = sink_list[sink_index].name; + snprintf(buf256, 256, "sink_name=%s sink_properties=device.description=%s", sink_name, sink_name); + pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink", buf256, index_callback, NULL); + state++; + } + sink_index++; + break; + case 4: if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { pa_operation_unref(pa_op); - state = 4; + state = 3; } else pa_mainloop_iterate(pa_names_ml, 1, NULL); break; - // The following loads modules for digital input and output if they are not present. - case 4: - if ( ! have_QuiskDigitalInput) { - pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink", - "sink_name=QuiskDigitalInput sink_properties=device.description=QuiskDigitalInput", - index_callback, NULL); - state = 5; - pa_mainloop_iterate(pa_names_ml, 1, NULL); - } - else - state = 6; - break; case 5: - if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { - pa_operation_unref(pa_op); - state = 6; - } - else - pa_mainloop_iterate(pa_names_ml, 1, NULL); + pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt); + state++; + pa_mainloop_iterate(pa_names_ml, 1, NULL); break; case 6: - if ( ! have_QuiskDigitalOutput) { - pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink", - "sink_name=QuiskDigitalOutput sink_properties=device.description=QuiskDigitalOutput", - index_callback, NULL); - state = 7; - pa_mainloop_iterate(pa_names_ml, 1, NULL); - } - else - state = 9; - break; - case 7: if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { pa_operation_unref(pa_op); state = 9;