1568 lines
52 KiB
C
Executable File
1568 lines
52 KiB
C
Executable File
#include <Python.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <sys/time.h>
|
|
#include <complex.h>
|
|
#include <fftw3.h>
|
|
#include "quisk.h"
|
|
#include <sys/types.h>
|
|
#include "microphone.h"
|
|
#include "filter.h"
|
|
#include "freedv.h"
|
|
|
|
#ifdef MS_WINDOWS
|
|
#include <Winsock2.h>
|
|
static int mic_cleanup = 0; // must clean up winsock
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#endif
|
|
|
|
#define DEBUG 0
|
|
#define DEBUG_LEVEL 0
|
|
|
|
#if DEBUG_LEVEL || DEBUG_IO || DEBUG
|
|
static int debug_timer = 1; // count up number of samples
|
|
#endif
|
|
|
|
// The microphone samples must be 48000 sps or 8000 sps. The output sample
|
|
// rate is always MIC_OUT_RATE samples per second
|
|
|
|
// FM needs pre-emphasis and de-emphasis. See vk1od.net/FM/FM.htm for details.
|
|
// For IIR design, see http://www.abvolt.com/research/publications2.htm.
|
|
|
|
// Microhone preemphasis: boost high frequencies 0.00 to 1.00
|
|
double quisk_mic_preemphasis;
|
|
// Microphone clipping; try 3.0 or 4.0
|
|
double quisk_mic_clip;
|
|
|
|
// If true, decimate 48000 sps mic samples to 8000 sps for processing
|
|
#define DECIM_8000 1
|
|
|
|
struct alc {
|
|
complex double * buffer;
|
|
int buf_size;
|
|
int index;
|
|
int block_index;
|
|
int counter;
|
|
int fault;
|
|
double max_magn;
|
|
double gain_now[20];
|
|
double gain_max;
|
|
double gain_min;
|
|
double gain_change;
|
|
double next_change;
|
|
double final_gain;
|
|
} ;
|
|
|
|
// These are external:
|
|
int mic_max_display; // display value of maximum microphone signal level 0 to 2**15 - 1
|
|
int quiskSpotLevel = -1; // level is -1 for Spot button Off; else the Spot level 0 to 1000.
|
|
int quiskImdLevel = 500; // level for rxMode IMD, 0 to 1000
|
|
|
|
static SOCKET mic_socket = INVALID_SOCKET; // send microphone samples to a socket
|
|
static double mic_agc_level = 0.10; // Mic levels below this are noise and are ignored
|
|
|
|
static int mic_level; // maximum microphone signal level for display
|
|
static int mic_timer; // time to display maximum mic level
|
|
static int align4; // add two bytes to start of audio samples to align to 4 bytes
|
|
static double modulation_index = 1.6; // For FM transmit, the modulation index
|
|
|
|
static int is_vox = 0; // Is the VOX level exceeded?
|
|
static int vox_level = CLIP16; // VOX trigger level as a number 0 to CLIP16
|
|
static int timeVOX = 2000; // VOX hang time in milliseconds
|
|
static int tx_sample_rate = 48000; // Used for SoapySDR
|
|
static int reverse_tx_sideband;
|
|
|
|
static int doTxCorrect = 0; // Corrections for UDP sample transmit
|
|
static double TxCorrectLevel;
|
|
static complex TxCorrectDc;
|
|
|
|
// Used for the Hermes protocol
|
|
#define HERMES_TX_BUF_SAMPLES 4800 // buffer size in I/Q samples (two shorts)
|
|
#define HERMES_TX_BUF_SHORTS (HERMES_TX_BUF_SAMPLES * 2)
|
|
static int hermes_read_index; // index to read from buffer
|
|
static int hermes_write_index; // index to write to buffer
|
|
static int hermes_num_samples; // number of samples in the buffer
|
|
static short hermes_buf[HERMES_TX_BUF_SHORTS]; // buffer to store Tx I/Q samples waiting to be sent at 48 ksps
|
|
static int hermes_filter_rx; // hermes filter to use for Rx
|
|
static int hermes_filter_tx; // hermes filter to use for Tx
|
|
static int alex_hpf_rx; // Alex HPF to use for Rx
|
|
static int alex_hpf_tx; // Alex HPF to use for Tx
|
|
static int alex_lpf_rx; // Alex LPF to use for Rx
|
|
static int alex_lpf_tx; // Alex LPF to use for Tx
|
|
|
|
#define TX_BLOCK_SHORTS 600 // transmit UDP packet with this many shorts (two bytes) (perhaps + 1)
|
|
#define MIC_MAX_HOLD_TIME 400 // Time to hold the maximum mic level on the Status screen in milliseconds
|
|
|
|
// If USE_GET_SIN is not zero, replace mic samples with a sin wave at a
|
|
// frequency determined by the sidetone slider and an amplitude determined
|
|
// by the Spot button level. LEVEL FAILS
|
|
// If USE_GET_SIN is 1, pass these samples through the transmit filters.
|
|
// If USE_GET_SIN is 2, transmit these samples directly.
|
|
#define USE_GET_SIN 0
|
|
|
|
// If USE_2TONE is not zero, replace samples with a 2-tone test signal.
|
|
#define USE_2TONE 0
|
|
|
|
#if USE_GET_SIN
|
|
static void get_sin(complex double * cSamples, int count)
|
|
{ // replace mic samples with a sin wave
|
|
int i;
|
|
double freq;
|
|
complex double phase1; // Phase increment
|
|
static complex double vector1 = CLIP32 / 2;
|
|
|
|
// Use the sidetone slider 0 to 1000 to set frequency
|
|
//freq = (quisk_sidetoneCtrl - 500) / 1000.0 * MIC_OUT_RATE;
|
|
freq = quisk_sidetoneCtrl * 5;
|
|
freq = ((int)freq / 50) * 50;
|
|
#if USE_GET_SIN == 2
|
|
phase1 = cexp(I * 2.0 * M_PI * freq / MIC_OUT_RATE);
|
|
count *= MIC_OUT_RATE / quisk_sound_state.mic_sample_rate;
|
|
#else
|
|
phase1 = cexp(I * 2.0 * M_PI * freq / quisk_sound_state.mic_sample_rate);
|
|
#endif
|
|
for (i = 0; i < count; i++) {
|
|
vector1 *= phase1;
|
|
cSamples[i] = vector1;
|
|
}
|
|
#if DEBUG_IO || DEBUG
|
|
if (debug_timer == 0)
|
|
printf ("get_sin freq %.0lf\n", freq);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if USE_2TONE
|
|
static void get_2tone(complex double * cSamples, int count)
|
|
{ // replace mic samples
|
|
int i;
|
|
static complex double phase1=0, phase2; // Phase increment
|
|
static complex double vector1;
|
|
static complex double vector2;
|
|
|
|
if (phase1 == 0) { // initialize
|
|
phase1 = cexp((I * 2.0 * M_PI * IMD_TONE_1) / quisk_sound_state.mic_sample_rate);
|
|
phase2 = cexp((I * 2.0 * M_PI * IMD_TONE_2) / quisk_sound_state.mic_sample_rate);
|
|
vector1 = CLIP32 / 2.0;
|
|
vector2 = CLIP32 / 2.0;
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
vector1 *= phase1;
|
|
vector2 *= phase2;
|
|
cSamples[i] = (vector1 + vector2);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static double CcmPeak(double * dsamples, complex double * csamples, int count)
|
|
{
|
|
int i, j;
|
|
complex double csample;
|
|
double dtmp, dsample, newlevel, oldlevel;
|
|
static double out_short, out_long;
|
|
static struct Ccmpr {
|
|
int buf_size;
|
|
int index_read;
|
|
double themax;
|
|
double level;
|
|
double * d_samp;
|
|
complex double * c_samp;
|
|
double * levl;
|
|
} dat = {0};
|
|
|
|
if ( ! dat.buf_size) { // initialize; the sample rate is 8000
|
|
dat.buf_size = 8000 * 30 / 1000; // total delay in samples
|
|
dat.index_read = 0; // index to output; and then write a new sample here
|
|
dat.themax = 1.0; // maximum level in the buffer
|
|
dat.level = 1.0; // current output level
|
|
dat.d_samp = (double *) malloc(dat.buf_size * sizeof(double)); // buffer for double samples
|
|
dat.c_samp = (complex double *) malloc(dat.buf_size * sizeof(complex double)); // buffer for complex samples
|
|
dat.levl = (double *) malloc(dat.buf_size * sizeof(double)); // magnitude of the samples
|
|
for (i = 0; i < dat.buf_size; i++) {
|
|
dat.d_samp[i] = 0;
|
|
dat.c_samp[i] = 0;
|
|
dat.levl[i] = 1.0;
|
|
}
|
|
dtmp = 1.0 / 8000; // sample time
|
|
out_short = 1.0 - exp(- dtmp / 0.010); // short time constant
|
|
out_long = 1.0 - exp(- dtmp / 3.000); // long time constant
|
|
return 1.0;
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
if (dsamples) {
|
|
dsample = dsamples[i];
|
|
dsamples[i] = dat.d_samp[dat.index_read] / dat.level; // FIFO output
|
|
dat.d_samp[dat.index_read] = dsample; // write new sample at read index
|
|
newlevel = fabs(dsample);
|
|
}
|
|
else {
|
|
csample = csamples[i];
|
|
csamples[i] = dat.c_samp[dat.index_read] / dat.level; // FIFO output
|
|
dat.c_samp[dat.index_read] = csample; // write new sample at read index
|
|
newlevel = cabs(csample);
|
|
}
|
|
oldlevel = dat.levl[dat.index_read];
|
|
dat.levl[dat.index_read] = newlevel;
|
|
if (newlevel < dat.themax && oldlevel < dat.themax) { // some other sample is the maximum
|
|
// no change to dat.themax
|
|
}
|
|
else if (newlevel > dat.themax && newlevel > oldlevel) { // newlevel is the maximum
|
|
dat.themax = newlevel;
|
|
}
|
|
else { // search for the maximum level
|
|
dat.themax = 0; // Find the maximim level in the buffer
|
|
for (j = 0; j < dat.buf_size; j++) {
|
|
if (dat.levl[j] > dat.themax)
|
|
dat.themax = dat.levl[j];
|
|
}
|
|
}
|
|
// Increase dat.level if the maximum level is greater than 1.0;
|
|
// decrease it slowly back to 1.0 if it is lower. Output is modulated by dat.level.
|
|
if (dat.themax > 1.0) // increase rapidly to the peak level
|
|
dat.level = dat.level * (1.0 - out_short) + dat.themax * out_short;
|
|
else // decrease slowly back to 1.0
|
|
dat.level = dat.level * (1.0 - out_long) + 1.0 * out_long;
|
|
if (++dat.index_read >= dat.buf_size)
|
|
dat.index_read = 0;
|
|
}
|
|
return dat.level;
|
|
}
|
|
|
|
static void init_alc(struct alc * pt, int size)
|
|
{ // Call first to set the buffer size. Then call to initialize the structure on each key down.
|
|
int i;
|
|
|
|
if (pt->buffer == NULL) {
|
|
pt->buf_size = size; // do not change the size
|
|
pt->buffer = (complex double *)malloc(size * sizeof(complex double));
|
|
for (i = 0; i < 20; i++) // initial gain by rx_mode
|
|
switch(i) {
|
|
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;
|
|
break;
|
|
default:
|
|
pt->gain_now[i] = 1.0;
|
|
break;
|
|
}
|
|
}
|
|
pt->index = 0;
|
|
pt->block_index = 0;
|
|
pt->counter = 0;
|
|
pt->fault = 0;
|
|
pt->max_magn = 0;
|
|
pt->gain_max = 3.0;
|
|
pt->gain_min = 0.1;
|
|
pt->gain_change = 0;
|
|
pt->next_change = 0;
|
|
pt->final_gain = 0;
|
|
for (i = 0; i < pt->buf_size; i++)
|
|
pt->buffer[i] = 0;
|
|
}
|
|
|
|
static void process_alc(complex double * cSamples, int count, struct alc * pt, rx_mode_type rx_mode)
|
|
{ // Automatic Level Control (ALC)
|
|
int i;
|
|
double d, magn;
|
|
complex double csamp;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
csamp = cSamples[i]; // new sample to add to buffer
|
|
cSamples[i] = pt->buffer[pt->index] * pt->gain_now[rx_mode]; // remove sample from buffer and apply gain
|
|
#if DEBUG_LEVEL || DEBUG_IO || DEBUG
|
|
magn = cabs(cSamples[i]);
|
|
if (magn >= CLIP16)
|
|
printf("ALC clip gain %9.6f level %9.6f\n", pt->gain_now[rx_mode], magn / CLIP16);
|
|
#endif
|
|
pt->buffer[pt->index] = csamp; // add new sample to buffer
|
|
magn = cabs(csamp); // measure new sample
|
|
if (magn * (pt->gain_now[rx_mode] + pt->gain_change * pt->buf_size) > (CLIP16 - 10)) {
|
|
pt->gain_change = ((CLIP16 - 10) / magn - pt->gain_now[rx_mode]) / pt->buf_size;
|
|
pt->final_gain = pt->gain_now[rx_mode] + pt->gain_change * pt->buf_size;
|
|
if (pt->final_gain > pt->gain_max) {
|
|
pt->final_gain = pt->gain_max;
|
|
pt->gain_change = (pt->final_gain - pt->gain_now[rx_mode]) / pt->buf_size;
|
|
}
|
|
else if (pt->final_gain < pt->gain_min) {
|
|
pt->final_gain = pt->gain_min;
|
|
pt->gain_change = (pt->final_gain - pt->gain_now[rx_mode]) / pt->buf_size;
|
|
}
|
|
pt->block_index = pt->index;
|
|
pt->counter = 0;
|
|
pt->fault = 0;
|
|
pt->next_change = 1E10;
|
|
//printf("ALC DEC: gain %9.6f change %9.6f final gain %9.6f\n", pt->gain_now[rx_mode], pt->gain_change, pt->final_gain);
|
|
}
|
|
else if (pt->index == pt->block_index) {
|
|
//d = cabs(cSamples[i]) / (CLIP16 - 10);
|
|
//printf("ALC Fin: gain %9.6f out level %9.6f\n", pt->gain_now[rx_mode], d);
|
|
#if 0
|
|
alc_start_gain = alc_gain;
|
|
k = alc_block_index;
|
|
for (j = 1; j < pt->buf_size; j++) {
|
|
if (++k >= pt->buf_size)
|
|
k = 0;
|
|
magn = cabs(alc_buffer[k]);
|
|
if (magn < 100) {
|
|
alc_fault++;
|
|
continue;
|
|
}
|
|
else {
|
|
d = ((CLIP16 - 10) / magn - alc_start_gain) / j;
|
|
if ( alc_next_change > d)
|
|
alc_next_change = d;
|
|
}
|
|
}
|
|
#endif
|
|
d = 5.0; // number of seconds to double gain
|
|
d = 1.0 / (48000.0 * d);
|
|
if (pt->next_change > d)
|
|
pt->next_change = d;
|
|
if (pt->next_change != 1E10 && pt->fault < pt->buf_size - 10) {
|
|
pt->gain_change = pt->next_change;
|
|
}
|
|
pt->final_gain = pt->gain_now[rx_mode] + pt->gain_change * pt->buf_size;
|
|
if (pt->final_gain > pt->gain_max) {
|
|
pt->final_gain = pt->gain_max;
|
|
pt->gain_change = (pt->final_gain - pt->gain_now[rx_mode]) / pt->buf_size;
|
|
}
|
|
else if (pt->final_gain < pt->gain_min) {
|
|
pt->final_gain = pt->gain_min;
|
|
pt->gain_change = (pt->final_gain - pt->gain_now[rx_mode]) / pt->buf_size;
|
|
}
|
|
//printf("ALC New: gain %9.6f change %9.6f final gain %9.6f\n", pt->gain_now[rx_mode], pt->gain_change, pt->final_gain);
|
|
pt->fault = 0;
|
|
pt->counter = 0;
|
|
pt->next_change = 1E10;
|
|
}
|
|
else {
|
|
if (magn < 100) {
|
|
pt->fault++;
|
|
}
|
|
else {
|
|
d = ((CLIP16 - 10) / magn - pt->final_gain) / ++pt->counter;
|
|
if ( pt->next_change > d)
|
|
pt->next_change = d;
|
|
}
|
|
}
|
|
pt->gain_now[rx_mode] += pt->gain_change;
|
|
if (++pt->index >= pt->buf_size)
|
|
pt->index = 0;
|
|
}
|
|
#if DEBUG_LEVEL || DEBUG_IO || DEBUG
|
|
for (i = 0; i < count; i++) {
|
|
magn = cabs(cSamples[i]);
|
|
if (pt->max_magn < magn)
|
|
pt->max_magn = magn;
|
|
}
|
|
if (debug_timer == 0) {
|
|
printf("ALC Out: gain %9.6f max lvl%9.6f final gain %9.6f\n", pt->gain_now[rx_mode], pt->max_magn / CLIP16, pt->final_gain);
|
|
pt->max_magn = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int tx_filter(complex double * filtered, int count)
|
|
{ // Input samples are creal(filtered), output is filtered. The input rate must be 8000 or 48000 sps.
|
|
int i, is_ssb;
|
|
int sample_rate = 8000;
|
|
double dsample, dtmp, magn;
|
|
complex double csample;
|
|
static double inMax=0.3;
|
|
static double x_1=0;
|
|
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
|
|
static int samples_size = 0;
|
|
static double * dsamples = NULL;
|
|
static complex double * csamples = NULL;
|
|
static double time_long, time_short;
|
|
static struct quisk_dFilter filtDecim, dfiltInterp;
|
|
static struct quisk_dFilter filtAudio1, filtAudio2, dfiltAudio3;
|
|
static struct quisk_cFilter cfiltAudio3, cfiltInterp;
|
|
static struct quisk_dFilter filter1={NULL}, filter2;
|
|
#if DEBUG_IO || DEBUG
|
|
char * clip;
|
|
static double dbOut = 0, Level0 = 0, Level1 = 0, Level2 = 0, Level3 = 0, Level4 = 0;
|
|
#endif
|
|
is_ssb = (rxMode == LSB || rxMode == USB);
|
|
if (!filtered) { // initialization
|
|
if (! filter1.dCoefs) {
|
|
quisk_filt_dInit(&filter1, quiskMicFilt8Coefs, sizeof(quiskMicFilt8Coefs)/sizeof(double));
|
|
quisk_filt_dInit(&filter2, quiskMicFilt8Coefs, sizeof(quiskMicFilt8Coefs)/sizeof(double));
|
|
quisk_filt_dInit(&filtDecim, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
|
quisk_filt_dInit(&dfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
|
quisk_filt_cInit(&cfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
|
quisk_filt_dInit(&filtAudio1, quiskFiltTx8kAudioB, sizeof(quiskFiltTx8kAudioB)/sizeof(double));
|
|
quisk_filt_dInit(&filtAudio2, quiskFiltTx8kAudioB, sizeof(quiskFiltTx8kAudioB)/sizeof(double));
|
|
quisk_filt_dInit(&dfiltAudio3, quiskFiltTx8kAudioB, sizeof(quiskFiltTx8kAudioB)/sizeof(double));
|
|
quisk_filt_cInit(&cfiltAudio3, quiskFiltTx8kAudioB, sizeof(quiskFiltTx8kAudioB)/sizeof(double));
|
|
dtmp = 1.0 / sample_rate; // sample time
|
|
time_long = 1.0 - exp(- dtmp / 3.000);
|
|
time_short = 1.0 - exp(- dtmp / 0.005);
|
|
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
|
|
aaa = 1.0 / (2.0 * (Xmin - Xmax)); // quadratic
|
|
bbb = -2.0 * aaa * Xmax;
|
|
ccc = Ymax - aaa * Xmax * Xmax - bbb * Xmax;
|
|
#if DEBUG_IO || DEBUG
|
|
printf("Compress to %.2lf dB from %.2lf to %.2lf dB\n",
|
|
20 * log10(Ymax), 20 * log10(Xmin), 20 * log10(Xmax));
|
|
#endif
|
|
}
|
|
if (is_ssb) {
|
|
quisk_filt_tune(&filter1, 1650.0 / sample_rate, rxMode != LSB);
|
|
quisk_filt_tune(&filter2, 1650.0 / sample_rate, rxMode != LSB);
|
|
}
|
|
return 0;
|
|
}
|
|
// check size of dsamples[] and csamples[] buffer
|
|
if (count > samples_size) {
|
|
samples_size = count * 2;
|
|
if (dsamples)
|
|
free(dsamples);
|
|
if (csamples)
|
|
free(csamples);
|
|
dsamples = (double *)malloc(samples_size * sizeof(double));
|
|
csamples = (complex double *)malloc(samples_size * sizeof(complex double));
|
|
}
|
|
// copy to dsamples[], normalize to +/- 1.0
|
|
for (i = 0; i < count; i++)
|
|
dsamples[i] = creal(filtered[i]) / CLIP16;
|
|
// Decimate to 8000 Hz
|
|
if (quisk_sound_state.mic_sample_rate != sample_rate)
|
|
count = quisk_dDecimate(dsamples, count, &filtDecim, quisk_sound_state.mic_sample_rate / sample_rate);
|
|
// restrict bandwidth 300 to 2700 Hz
|
|
count = quisk_dFilter(dsamples, count, &filtAudio1);
|
|
#if DEBUG_IO || DEBUG
|
|
// Measure peak input audio level
|
|
for (i = 0; i < count; i++) {
|
|
magn = fabs(dsamples[i]);
|
|
if (magn > Level0)
|
|
Level0 = magn;
|
|
}
|
|
#endif
|
|
// high pass filter for preemphasis: See Radcom, January 2010, page 76.
|
|
// quisk_mic_preemphasis == 1 was measured as 6 dB / octave.
|
|
// gain at 800 Hz was measured as 0.104672.
|
|
for (i = 0; i < count; i++) {
|
|
dtmp = dsamples[i];
|
|
dsamples[i] = dtmp - quisk_mic_preemphasis * x_1;
|
|
x_1 = dtmp; // delayed sample
|
|
dsamples[i] *= 2; // compensate for loss
|
|
#if DEBUG_IO || DEBUG
|
|
magn = fabs(dsamples[i]);
|
|
if (magn > Level1)
|
|
Level1 = magn;
|
|
#endif
|
|
}
|
|
if (is_ssb) { // SSB
|
|
// FIR bandpass filter; separate into I and Q
|
|
for (i = 0; i < count; i++) {
|
|
csample = quisk_dC_out(dsamples[i], &filter1) * 2.0; // filter loss 0.5
|
|
// Measure average peak input audio level and normalize
|
|
magn = cabs(csample);
|
|
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;
|
|
csample /= inMax;
|
|
magn /= inMax;
|
|
#if DEBUG_IO || DEBUG
|
|
if (magn > Level2)
|
|
Level2 = magn;
|
|
#endif
|
|
// Audio compression.
|
|
csample *= quisk_mic_clip;
|
|
magn *= quisk_mic_clip;
|
|
if (magn > 1.0)
|
|
csample = csample / magn;
|
|
dsamples[i] = creal(csample);
|
|
}
|
|
}
|
|
else { // AM and FM
|
|
// Measure average peak input audio level and normalize
|
|
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 /= inMax;
|
|
magn /= inMax;
|
|
#if DEBUG_IO || DEBUG
|
|
if (magn > Level2)
|
|
Level2 = magn;
|
|
#endif
|
|
// Audio compression.
|
|
dsample *= quisk_mic_clip;
|
|
magn *= quisk_mic_clip;
|
|
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);
|
|
}
|
|
}
|
|
// remove clipping distortion; restrict bandwidth 300 to 2700 Hz
|
|
count = quisk_dFilter(dsamples, count, &filtAudio2);
|
|
if (is_ssb) { // SSB
|
|
// FIR bandpass filter; separate into I and Q
|
|
for (i = 0; i < count; i++) {
|
|
csamples[i] = quisk_dC_out(dsamples[i], &filter2) * 2.0; // filter loss 0.5
|
|
#if DEBUG_IO || DEBUG
|
|
magn = cabs(csamples[i]);
|
|
if (magn > Level3)
|
|
Level3 = magn;
|
|
#endif
|
|
}
|
|
// round off peaks
|
|
CcmPeak(NULL, csamples, count);
|
|
#if DEBUG_IO || DEBUG
|
|
for (i = 0; i < count; i++) {
|
|
magn = cabs(csamples[i]);
|
|
if (magn > Level4)
|
|
Level4 = magn;
|
|
}
|
|
#endif
|
|
// remove clipping distortion
|
|
count = quisk_cDecimate(csamples, count, &cfiltAudio3, 1);
|
|
// Interpolate up to 48000
|
|
if (MIC_OUT_RATE != sample_rate)
|
|
count = quisk_cInterpolate(csamples, count, &cfiltInterp, MIC_OUT_RATE / sample_rate);
|
|
// convert back to 16 bits
|
|
for (i = 0; i < count; i++) {
|
|
filtered[i] = csamples[i] * CLIP16;
|
|
#if DEBUG_IO || DEBUG
|
|
magn = cabs(csamples[i]);
|
|
if (magn > dbOut)
|
|
dbOut = magn;
|
|
#endif
|
|
}
|
|
}
|
|
else { // AM and FM
|
|
#if DEBUG_IO || DEBUG
|
|
for (i = 0; i < count; i++) {
|
|
magn = fabs(dsamples[i]);
|
|
if (magn > Level3)
|
|
Level3 = magn;
|
|
}
|
|
#endif
|
|
// round off peaks
|
|
CcmPeak(dsamples, NULL, count);
|
|
#if DEBUG_IO || DEBUG
|
|
for (i = 0; i < count; i++) {
|
|
magn = fabs(dsamples[i]);
|
|
if (magn > Level4)
|
|
Level4 = magn;
|
|
}
|
|
#endif
|
|
// remove clipping distortion
|
|
count = quisk_dFilter(dsamples, count, &dfiltAudio3);
|
|
// Interpolate up to 48000
|
|
if (MIC_OUT_RATE != sample_rate)
|
|
count = quisk_dInterpolate(dsamples, count, &dfiltInterp, MIC_OUT_RATE / sample_rate);
|
|
// convert back to 16 bits
|
|
for (i = 0; i < count; i++) {
|
|
filtered[i] = dsamples[i] * CLIP16;
|
|
#if DEBUG_IO || DEBUG
|
|
magn = fabs(dsamples[i]);
|
|
if (magn > dbOut)
|
|
dbOut = magn;
|
|
#endif
|
|
}
|
|
}
|
|
#if DEBUG_IO || DEBUG
|
|
if (debug_timer == 0) {
|
|
if (dbOut > 1.0)
|
|
clip = "Clip";
|
|
else
|
|
clip = "";
|
|
dbOut = 20 * log10(dbOut);
|
|
printf ("pre %3.1lf dB clip %2.0lf InMax %6.2lf Level0 %6.2lf Level1 %6.2lf Level2 %6.2lf Level3 %6.2lf Level4 %6.2lf dbOut %6.2lf %s\n",
|
|
quisk_mic_preemphasis, 20 * log10(quisk_mic_clip), 20 * log10(inMax), 20 * log10(Level0),
|
|
20 * log10(Level1), 20 * log10(Level2), 20 * log10(Level3), 20 * log10(Level4), dbOut, clip);
|
|
Level0 = Level1 = Level2 = Level3 = Level4 = dbOut = 0;
|
|
}
|
|
//QuiskPrintTime(" tx_filter", 2);
|
|
#endif
|
|
return count;
|
|
}
|
|
|
|
static int tx_filter_digital(complex double * filtered, int count)
|
|
{ // Input samples are creal(filtered), output is filtered.
|
|
// This filter has minimal processing and is used for digital modes.
|
|
int i;
|
|
static int do_init = 1;
|
|
|
|
static struct quisk_dFilter filter1;
|
|
if (do_init) { // initialization
|
|
do_init = 0;
|
|
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);
|
|
return 0;
|
|
}
|
|
for (i = 0; i < count; i++) // FIR bandpass filter; separate into I and Q
|
|
filtered[i] = quisk_dC_out(creal(filtered[i]), &filter1) * 2.00; // tuned filter loss 0.5
|
|
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
|
|
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.
|
|
int i;
|
|
int sample_rate = 8000;
|
|
double dtmp, magn, dsample;
|
|
static int samples_size = 0;
|
|
static int last_mode;
|
|
static int do_init = 1;
|
|
static double aaa, bbb, ccc, Xmin, Xmax, Ymax;
|
|
static double time_long, time_short;
|
|
static double inMax=0.3;
|
|
static double * dsamples = NULL;
|
|
static struct quisk_cFilter filter2;
|
|
static struct quisk_dFilter filtDecim;
|
|
static struct quisk_cFilter cfiltInterp;
|
|
|
|
if (do_init) { // initialization
|
|
//QuiskWavWriteOpen(&hWav, "jim.wav", 3, 1, 4, 8000, 1.0 / CLIP16);
|
|
do_init = 0;
|
|
quisk_filt_cInit(&filter2, quiskFilt53D2Coefs, sizeof(quiskFilt53D2Coefs)/sizeof(double)); // pass 1500, stop 1800
|
|
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / 8000, 1); // upper sideband
|
|
last_mode = 11;
|
|
quisk_filt_dInit(&filtDecim, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double)); // pass 3000, stop 4000
|
|
quisk_filt_cInit(&cfiltInterp, quiskLpFilt48Coefs, sizeof(quiskLpFilt48Coefs)/sizeof(double));
|
|
dtmp = 1.0 / sample_rate; // sample time
|
|
time_long = 1.0 - exp(- dtmp / 3.000);
|
|
time_short = 1.0 - exp(- dtmp / 0.005);
|
|
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;
|
|
}
|
|
if (last_mode != rxMode) { // change to sideband
|
|
if (rxMode == FDV_U) { // upper sideband
|
|
last_mode = rxMode;
|
|
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / 8000, 1);
|
|
}
|
|
else if (rxMode == FDV_L) { // lower sideband
|
|
last_mode = rxMode;
|
|
quisk_filt_tune((struct quisk_dFilter *)&filter2, 1600.0 / 8000, 0);
|
|
}
|
|
}
|
|
if ( ! filtered)
|
|
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
|
|
if (quisk_sound_state.mic_sample_rate != sample_rate)
|
|
count = quisk_dDecimate(dsamples, count, &filtDecim, quisk_sound_state.mic_sample_rate / sample_rate);
|
|
// Measure average peak input audio level and limit
|
|
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);
|
|
//quisk_calc_audio_graph(CLIP16, filtered, NULL, count, 0);
|
|
if (freedv_current_mode != FREEDV_MODE_700D) // 700D has its own filter
|
|
count = quisk_cCDecimate(filtered, count, &filter2, 1);
|
|
// Interpolate up to 48000
|
|
if (MIC_OUT_RATE != sample_rate)
|
|
count = quisk_cInterpolate(filtered, count, &cfiltInterp, MIC_OUT_RATE / 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
|
|
// of QS.get_filter().
|
|
int i, j, k;
|
|
int freq, time;
|
|
PyObject * tuple2;
|
|
complex double cx;
|
|
double scale;
|
|
double * average, * fft_window, * bufI, * bufQ;
|
|
fftw_complex * samples, * pt; // complex data for fft
|
|
fftw_plan plan; // fft plan
|
|
double phase, delta;
|
|
int nTaps = 325;
|
|
|
|
if (!PyArg_ParseTuple (args, ""))
|
|
return NULL;
|
|
|
|
// Create space for the fft of size data_width
|
|
pt = samples = (fftw_complex *) fftw_malloc(sizeof(fftw_complex) * data_width);
|
|
plan = fftw_plan_dft_1d(data_width, pt, pt, FFTW_FORWARD, FFTW_MEASURE);
|
|
average = (double *) malloc(sizeof(double) * (data_width + nTaps));
|
|
fft_window = (double *) malloc(sizeof(double) * data_width);
|
|
bufI = (double *) malloc(sizeof(double) * nTaps);
|
|
bufQ = (double *) malloc(sizeof(double) * nTaps);
|
|
|
|
for (i = 0, j = -data_width / 2; i < data_width; i++, j++) // Hanning
|
|
fft_window[i] = 0.5 + 0.5 * cos(2. * M_PI * j / data_width);
|
|
|
|
for (i = 0; i < data_width + nTaps; i++)
|
|
average[i] = 0.5; // Value for freq == 0
|
|
for (freq = 1; freq < data_width / 2.0 - 10.0; freq++) {
|
|
//freq = data_width * 0.2 / 48.0;
|
|
delta = 2 * M_PI / data_width * freq;
|
|
phase = 0;
|
|
// generate some initial samples to fill the filter pipeline
|
|
for (time = 0; time < data_width + nTaps; time++) {
|
|
average[time] += cos(phase); // current sample
|
|
phase += delta;
|
|
if (phase > 2 * M_PI)
|
|
phase -= 2 * M_PI;
|
|
}
|
|
}
|
|
// now filter the signal using the transmit filter
|
|
tx_filter(NULL, 0); // initialize
|
|
scale = 1.0;
|
|
for (i = 0; i < data_width; i++)
|
|
if (fabs(average[i + nTaps]) > scale)
|
|
scale = fabs(average[i + nTaps]);
|
|
scale = CLIP16 / scale; // limit to CLIP16
|
|
for (i = 0; i < nTaps; i++)
|
|
samples[i] = average[i] * scale;
|
|
tx_filter(samples, nTaps); // process initial samples
|
|
for (i = 0; i < data_width; i++)
|
|
samples[i] = average[i + nTaps] * scale;
|
|
tx_filter(samples, data_width); // process the samples
|
|
|
|
for (i = 0; i < data_width; i++) // multiply by window
|
|
samples[i] *= fft_window[i];
|
|
fftw_execute(plan); // Calculate FFT
|
|
// Normalize and convert to log10
|
|
scale = 0.3 / data_width / scale;
|
|
for (k = 0; k < data_width; k++) {
|
|
cx = samples[k];
|
|
average[k] = cabs(cx) * scale;
|
|
if (average[k] <= 1e-7) // limit to -140 dB
|
|
average[k] = -7;
|
|
else
|
|
average[k] = log10(average[k]);
|
|
}
|
|
// Return the graph data
|
|
tuple2 = PyTuple_New(data_width);
|
|
i = 0;
|
|
// Negative frequencies:
|
|
for (k = data_width / 2; k < data_width; k++, i++)
|
|
PyTuple_SetItem(tuple2, i, PyFloat_FromDouble(20.0 * average[k]));
|
|
|
|
// Positive frequencies:
|
|
for (k = 0; k < data_width / 2; k++, i++)
|
|
PyTuple_SetItem(tuple2, i, PyFloat_FromDouble(20.0 * average[k]));
|
|
|
|
free(bufQ);
|
|
free(bufI);
|
|
free(average);
|
|
free(fft_window);
|
|
fftw_destroy_plan(plan);
|
|
fftw_free(samples);
|
|
|
|
return tuple2;
|
|
}
|
|
|
|
// Send samples using the Metis-Hermes protocol. A frame is 8 bytes: L/R audio and I/Q mic samples.
|
|
// All samples are 2 bytes. The 1032 byte UDP packet contains 63*2 radio sound samples, and 63*2 mic I/Q samples.
|
|
// Samples are sent synchronously with the input samples.
|
|
|
|
static void quisk_hermes_tx_reset(void)
|
|
{ // Reset the buffer to half full of zero samples
|
|
int i;
|
|
|
|
hermes_num_samples = HERMES_TX_BUF_SAMPLES / 2;
|
|
hermes_read_index = 0;
|
|
hermes_write_index = hermes_num_samples * 2;
|
|
for (i = 0; i < HERMES_TX_BUF_SHORTS; i++) // Put zero mic samples into the buffer
|
|
hermes_buf[i] = 0;
|
|
//printf("quisk_hermes_tx_reset: hermes_num_samples %d\n", hermes_num_samples);
|
|
}
|
|
|
|
static void quisk_hermes_tx_add(complex double * cSamples, int tx_count, int key_down)
|
|
{ // Add samples to the Tx buffer.
|
|
int i;
|
|
static int hermes_buf_has_samples=1;
|
|
|
|
if (key_down) { // add non-zero samples to buffer
|
|
hermes_buf_has_samples = 1;
|
|
}
|
|
else if (hermes_buf_has_samples) { // key is not down; reset buffer to zero
|
|
quisk_hermes_tx_reset();
|
|
hermes_buf_has_samples = 0;
|
|
return;
|
|
}
|
|
else { // key is not down but buffer is zeroed; just reset pointers
|
|
hermes_num_samples = HERMES_TX_BUF_SAMPLES / 2;
|
|
hermes_read_index = 0;
|
|
hermes_write_index = hermes_num_samples * 2;
|
|
return;
|
|
}
|
|
//printf("hermes_tx_add start: hermes_num_samples %d, tx_count %d\n", hermes_num_samples, tx_count);
|
|
if (hermes_num_samples + tx_count >= HERMES_TX_BUF_SAMPLES) { // no more space; throw away half the samples
|
|
quisk_udp_mic_error("Tx hermes buffer overflow");
|
|
//printf("Tx hermes buffer overflow: hermes_num_samples %d tx_count %d\n", hermes_num_samples, tx_count);
|
|
i = hermes_num_samples - HERMES_TX_BUF_SAMPLES / 2; // number of samples to remove
|
|
hermes_num_samples -= i;
|
|
hermes_read_index += i * 2;
|
|
if (hermes_read_index >= HERMES_TX_BUF_SHORTS)
|
|
hermes_read_index -= HERMES_TX_BUF_SHORTS;
|
|
}
|
|
hermes_num_samples += tx_count;
|
|
for (i = 0; i < tx_count; i++) { // Put transmit mic samples into the buffer
|
|
hermes_buf[hermes_write_index++] = (short)cimag(cSamples[i]);
|
|
hermes_buf[hermes_write_index++] = (short)creal(cSamples[i]);
|
|
if (hermes_write_index >= HERMES_TX_BUF_SHORTS)
|
|
hermes_write_index = 0;
|
|
}
|
|
//printf ("Buffer usage %.1f %%, hermes_num_samples %d, tx_count %d\n", 100.0 * hermes_num_samples / HERMES_TX_BUF_SAMPLES, hermes_num_samples, tx_count);
|
|
//printf("hermes_tx_add end: hermes_num_samples %d\n", hermes_num_samples);
|
|
}
|
|
|
|
void quisk_hermes_tx_send(int tx_socket, int * tx_records)
|
|
{ // Send one UDP block of mic samples using the Metis-Hermes protocol. Timing is from blocks received, rate is 48k.
|
|
// If the key is up we send samples anyway, but the samples are zero.
|
|
int i, offset, sent, ratio;
|
|
short s;
|
|
unsigned char sendbuf[1032];
|
|
unsigned char * pt_buf;
|
|
static unsigned int seq = 0;
|
|
static unsigned char C0_index = 0;
|
|
static int mox_bit=0; // On key up, send some zero samples before releasing the T/R relay to Rx.
|
|
static int mox_counter; // Timer for key-up delay.
|
|
unsigned int hlwp = 0;
|
|
|
|
//printf("hermes_tx_send start 1: hermes_num_samples %d\n", hermes_num_samples);
|
|
if (tx_records == NULL) {
|
|
seq = 0;
|
|
C0_index = 0;
|
|
quisk_hermes_tx_reset();
|
|
return;
|
|
}
|
|
if (quisk_is_key_down()) {
|
|
mox_bit = 1;
|
|
mox_counter = (int)((quiskKeyupDelay + 30) / 2.625 + 0.5); // 126 samples per block / 48000 == 2.625 msec
|
|
}
|
|
else { // Delay un-setting the MOX bit and continue sending zero-valued samples; when Tx signal is zero, unset MOX.
|
|
if (mox_bit) { // This allows the Tx signal to go to zero before switching T/R relay.
|
|
if (mox_counter > 0)
|
|
mox_counter--;
|
|
else
|
|
mox_bit = 0;
|
|
}
|
|
}
|
|
//printf("hermes_tx_send start 2: hermes_num_samples %d\n", hermes_num_samples);
|
|
ratio = quisk_sound_state.sample_rate / 48000; // send rate is 48 ksps
|
|
//printf ("quisk_hermes_tx_send ratio %d count %d\n", ratio, *tx_records);
|
|
if (*tx_records / ratio < 63 * 2) // tx_records is the number of samples received for each receiver
|
|
return;
|
|
// Send 63*2 Tx samples with control bytes
|
|
//printf ("Buffer usage %.1f %%\n", 100.0 * hermes_num_samples / HERMES_TX_BUF_SAMPLES);
|
|
//printf ("Tx quisk_hermes_tx_send ratio %d, count %d, samples %d\n", ratio, *tx_records, hermes_num_samples);
|
|
*tx_records -= 63 * 2 * ratio;
|
|
if (hermes_num_samples < 63 * 2) { // Not enough samples to send
|
|
//printf("Tx hermes buffer underflow: hermes_num_samples %d\n", hermes_num_samples);
|
|
quisk_udp_mic_error("Tx hermes buffer underflow");
|
|
quisk_hermes_tx_reset();
|
|
}
|
|
hermes_num_samples -= 63 * 2;
|
|
sendbuf[0] = 0xEF;
|
|
sendbuf[1] = 0xFE;
|
|
sendbuf[2] = 0x01;
|
|
sendbuf[3] = 0x02;
|
|
sendbuf[4] = seq >> 24 & 0xFF;
|
|
sendbuf[5] = seq >> 16 & 0xFF;
|
|
sendbuf[6] = seq >> 8 & 0xFF;
|
|
sendbuf[7] = seq & 0xFF;
|
|
seq++;
|
|
sendbuf[8] = 0x7F;
|
|
sendbuf[9] = 0x7F;
|
|
sendbuf[10] = 0x7F;
|
|
offset = C0_index * 4; // offset into quisk_pc_to_hermes is C0[7:1] * 4
|
|
sendbuf[11] = C0_index << 1 | mox_bit; // C0
|
|
sendbuf[12] = quisk_pc_to_hermes[offset++]; // C1
|
|
sendbuf[13] = quisk_pc_to_hermes[offset++]; // C2
|
|
sendbuf[14] = quisk_pc_to_hermes[offset++]; // C3
|
|
sendbuf[15] = quisk_pc_to_hermes[offset++]; // C4
|
|
if (C0_index == 0) { // Do not change receiver count without stopping Hermes and restarting
|
|
sendbuf[15] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
|
|
if (mox_bit) // send filter selection on J16
|
|
sendbuf[13] = hermes_filter_tx << 1;
|
|
else
|
|
sendbuf[13] = hermes_filter_rx << 1;
|
|
}
|
|
else if ( ! quisk_is_vna && C0_index == 9) {
|
|
if (mox_bit) { // send Alex filter selection
|
|
sendbuf[14] = alex_hpf_tx;
|
|
sendbuf[15] = alex_lpf_tx;
|
|
}
|
|
else {
|
|
sendbuf[14] = alex_hpf_rx;
|
|
sendbuf[15] = alex_lpf_rx;
|
|
}
|
|
}
|
|
if (++C0_index > 16)
|
|
C0_index = 0;
|
|
pt_buf = sendbuf + 16;
|
|
for (i = 0; i < 63; i++) { // add 63 samples
|
|
*pt_buf++ = 0x00; // Left/Right audio sample
|
|
*pt_buf++ = 0x00;
|
|
*pt_buf++ = 0x00;
|
|
*pt_buf++ = 0x00;
|
|
s = hermes_buf[hermes_read_index++];
|
|
*pt_buf++ = (s >> 8) & 0xFF; // Two bytes of I
|
|
*pt_buf++ = s & 0xFF;
|
|
s = hermes_buf[hermes_read_index++];
|
|
*pt_buf++ = (s >> 8) & 0xFF; // Two bytes of Q
|
|
*pt_buf++ = s & 0xFF;
|
|
if (hermes_read_index >= HERMES_TX_BUF_SHORTS)
|
|
hermes_read_index = 0;
|
|
}
|
|
sendbuf[520] = 0x7F;
|
|
sendbuf[521] = 0x7F;
|
|
sendbuf[522] = 0x7F;
|
|
|
|
// Changes for HermesLite v2 thanks to Steve, KF7O
|
|
if ((quisk_hermeslite_writepointer > 0) && (quisk_hermeslite_writeattempts++ % 8 == 0)) {
|
|
|
|
// Only send periodic hermeslite writes in second part of frame
|
|
hlwp = 5*(quisk_hermeslite_writepointer-1);
|
|
sendbuf[523] = quisk_hermeslite_writequeue[hlwp++] << 1 | mox_bit;
|
|
sendbuf[524] = quisk_hermeslite_writequeue[hlwp++];
|
|
sendbuf[525] = quisk_hermeslite_writequeue[hlwp++];
|
|
sendbuf[526] = quisk_hermeslite_writequeue[hlwp++];
|
|
sendbuf[527] = quisk_hermeslite_writequeue[hlwp++];
|
|
|
|
if ((sendbuf[523] & 0x80) == 0) {
|
|
// No acknowledge requested so fire and forget
|
|
quisk_hermeslite_writepointer--;
|
|
quisk_hermeslite_writeattempts = 0;
|
|
}
|
|
} else {
|
|
offset = C0_index * 4; // offset into quisk_pc_to_hermes is C0[7:1] * 4
|
|
sendbuf[523] = C0_index << 1 | mox_bit; // C0
|
|
sendbuf[524] = quisk_pc_to_hermes[offset++]; // C1
|
|
sendbuf[525] = quisk_pc_to_hermes[offset++]; // C2
|
|
sendbuf[526] = quisk_pc_to_hermes[offset++]; // C3
|
|
sendbuf[527] = quisk_pc_to_hermes[offset++]; // C4
|
|
if (C0_index == 0) {
|
|
sendbuf[527] = quisk_multirx_count << 3 | 0x04; // Send the old count, not the changed count
|
|
if (mox_bit) // send filter selection on J16
|
|
sendbuf[525] = hermes_filter_tx << 1;
|
|
else
|
|
sendbuf[525] = hermes_filter_rx << 1;
|
|
}
|
|
else if ( ! quisk_is_vna && C0_index == 9) {
|
|
if (mox_bit) { // send Alex filter selection
|
|
sendbuf[526] = alex_hpf_tx;
|
|
sendbuf[527] = alex_lpf_tx;
|
|
}
|
|
else {
|
|
sendbuf[526] = alex_hpf_rx;
|
|
sendbuf[527] = alex_lpf_rx;
|
|
}
|
|
}
|
|
if (++C0_index > 16)
|
|
C0_index = 0;
|
|
|
|
if (quisk_hermeslite_writepointer > 0) quisk_hermeslite_writeattempts++;
|
|
}
|
|
|
|
// Abort after 53/8 ~= 5 retries
|
|
if ((quisk_hermeslite_writepointer > 0) && (quisk_hermeslite_writeattempts > 53)) {
|
|
printf("ERROR: Maximum Hermes-Lite write attempts\n");
|
|
// Cancel entire write sequence
|
|
quisk_hermeslite_writepointer = 0;
|
|
quisk_hermeslite_writeattempts = 0;
|
|
}
|
|
|
|
pt_buf = sendbuf + 528;
|
|
for (i = 0; i < 63; i++) { // add 63 samples
|
|
*pt_buf++ = 0x00; // Left/Right audio sample
|
|
*pt_buf++ = 0x00;
|
|
*pt_buf++ = 0x00;
|
|
*pt_buf++ = 0x00;
|
|
s = hermes_buf[hermes_read_index++];
|
|
*pt_buf++ = (s >> 8) & 0xFF; // Two bytes of I
|
|
*pt_buf++ = s & 0xFF;
|
|
s = hermes_buf[hermes_read_index++];
|
|
*pt_buf++ = (s >> 8) & 0xFF; // Two bytes of Q
|
|
*pt_buf++ = s & 0xFF;
|
|
if (hermes_read_index >= HERMES_TX_BUF_SHORTS)
|
|
hermes_read_index = 0;
|
|
}
|
|
sent = send(tx_socket, (char *)sendbuf, 1032, 0);
|
|
if (sent != 1032)
|
|
quisk_udp_mic_error("Tx UDP socket error in Hermes");
|
|
//printf("hermes_tx_send end: hermes_num_samples %d\n", hermes_num_samples);
|
|
}
|
|
|
|
// udp_iq has an initial zero followed by the I/Q samples.
|
|
// The initial zero is sent iff align4 == 1.
|
|
|
|
static void transmit_udp(complex double * cSamples, int count)
|
|
{ // Send count samples using the HiQSDR protocol. Each sample is sent as two shorts (4 bytes) of I/Q data.
|
|
// Transmission is delayed until a whole block of data is available.
|
|
int i, sent;
|
|
static short udp_iq[TX_BLOCK_SHORTS + 1] = {0};
|
|
static int udp_size = 1;
|
|
|
|
if (mic_socket == INVALID_SOCKET)
|
|
return;
|
|
if ( ! cSamples) { // initialization
|
|
udp_size = 1;
|
|
udp_iq[0] = 0; // should not be necessary
|
|
return;
|
|
}
|
|
if (doTxCorrect) {
|
|
for (i = 0; i < count; i++)
|
|
cSamples[i] = cSamples[i] * TxCorrectLevel + TxCorrectDc;
|
|
}
|
|
for (i = 0; i < count; i++) { // transmit samples
|
|
udp_iq[udp_size++] = (short)creal(cSamples[i]);
|
|
udp_iq[udp_size++] = (short)cimag(cSamples[i]);
|
|
if (udp_size >= TX_BLOCK_SHORTS) { // check count
|
|
if (align4)
|
|
sent = send(mic_socket, (char *)udp_iq, udp_size * 2, 0);
|
|
else
|
|
sent = send(mic_socket, (char *)udp_iq + 1, --udp_size * 2, 0);
|
|
if (sent != udp_size * 2)
|
|
printf("Send socket returned %d\n", sent);
|
|
udp_size = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void transmit_mic_carrier(complex double * cSamples, int count, double level)
|
|
{ // send a CW carrier instead of mic samples
|
|
#if 1
|
|
// transmit a carrier equal to the number of samples
|
|
int i;
|
|
for (i = 0; i < count; i++)
|
|
cSamples[i] = level * CLIP16;
|
|
#else
|
|
// replace mic samples with a sin wave
|
|
int i;
|
|
double freq;
|
|
complex double phase1; // Phase increment
|
|
static complex double vector1 = CLIP16 / 2;
|
|
|
|
// Use the sidetone slider 0 to 1000 to set frequency
|
|
freq = quisk_sidetoneCtrl * 5;
|
|
freq = ((int)freq / 50) * 50;
|
|
phase1 = cexp(I * 2.0 * M_PI * freq / 48000.0);
|
|
//phase1 = conj(phase1);
|
|
for (i = 0; i < count; i++) {
|
|
vector1 *= phase1;
|
|
cSamples[i] = vector1 * level;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void transmit_mic_imd(complex double * cSamples, int count, double level)
|
|
{ // send a 2-tone test signal instead of mic samples
|
|
int i;
|
|
complex double v;
|
|
static complex double phase1=0, phase2; // Phase increment
|
|
static complex double vector1;
|
|
static complex double vector2;
|
|
|
|
if (phase1 == 0) { // initialize
|
|
phase1 = cexp((I * 2.0 * M_PI * IMD_TONE_1) / MIC_OUT_RATE);
|
|
phase2 = cexp((I * 2.0 * M_PI * IMD_TONE_2) / MIC_OUT_RATE);
|
|
vector1 = CLIP16 / 2.0;
|
|
vector2 = CLIP16 / 2.0;
|
|
}
|
|
for (i = 0; i < count; i++) { // transmit a carrier equal to the number of samples
|
|
vector1 *= phase1;
|
|
vector2 *= phase2;
|
|
v = level * (vector1 + vector2);
|
|
cSamples[i] = v;
|
|
}
|
|
}
|
|
|
|
int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int count)
|
|
{
|
|
int i, sample, maximum, interp, mic_interp, key_down;
|
|
double d, ctcss_delta, audio_scale, ctcss_scale;
|
|
static int key_was_down=0, key_down_counter=0;
|
|
static struct quisk_cFilter filtInterp={NULL};
|
|
static struct quisk_cFilter filtInterp2={NULL};
|
|
static struct quisk_cFilter filt240D4 = {NULL};
|
|
static struct quisk_cFilter filt300D6 = {NULL};
|
|
static struct quisk_cHB45Filter HalfBand = {NULL, 0, 0};
|
|
static double ctcss_angle=0;
|
|
static struct alc tx_alc = {NULL};
|
|
|
|
#if 0
|
|
// Measure soundcard actual sample rate
|
|
static time_t seconds = 0;
|
|
static int total = 0;
|
|
struct timeval tb;
|
|
static double dtime;
|
|
|
|
gettimeofday(&tb);
|
|
total += count;
|
|
if (seconds == 0) {
|
|
seconds = tb.tv_sec;
|
|
dtime = tb.tv_sec + 0.000001 * tb.tv_usec;
|
|
}
|
|
else if (tb.tv_sec - seconds > 4) {
|
|
printf("Mic soundcard rate %.3f\n", total / (tb.tv_sec + .000001 * tb.tv_usec - dtime));
|
|
seconds = tb.tv_sec;
|
|
printf("backlog %d, count %d\n", backlog, count);
|
|
}
|
|
#endif
|
|
|
|
#if DEBUG_IO || DEBUG
|
|
//QuiskPrintTime("", -1);
|
|
#endif
|
|
|
|
#if DEBUG_IO || DEBUG
|
|
double magn;
|
|
static double out_max=0;
|
|
#endif
|
|
#if DEBUG_LEVEL || DEBUG_IO || DEBUG
|
|
debug_timer += count;
|
|
if (debug_timer >= mic_sample_rate) // one second
|
|
debug_timer = 0;
|
|
#endif
|
|
|
|
// Microphone sample are input at mic_sample_rate. But after processing,
|
|
// the output rate is MIC_OUT_RATE.
|
|
interp = MIC_OUT_RATE / mic_sample_rate;
|
|
|
|
#if USE_GET_SIN
|
|
get_sin(cSamples, count); // Replace mic samples with a sin wave
|
|
#endif
|
|
#if USE_2TONE
|
|
get_2tone(cSamples, count); // Replace mic samples with a 2-tone test signal
|
|
#endif
|
|
// measure maximum microphone level
|
|
maximum = 1;
|
|
for (i = 0; i < count; i++) {
|
|
cSamples[i] *= (double)CLIP16 / CLIP32; // convert 32-bit samples to 16 bits
|
|
d = creal(cSamples[i]);
|
|
sample = (int)fabs(d);
|
|
if (sample > maximum)
|
|
maximum = sample;
|
|
}
|
|
// VOX processing
|
|
if (maximum > vox_level) {
|
|
is_vox = mic_sample_rate / 1000 * timeVOX; // reset timer to maximum
|
|
}
|
|
else if(is_vox) {
|
|
is_vox -= count; // decrement timer
|
|
if (is_vox < 0)
|
|
is_vox = 0;
|
|
}
|
|
// mic display level
|
|
if (maximum > mic_level)
|
|
mic_level = maximum;
|
|
mic_timer -= count; // time out the max microphone level to display
|
|
if (mic_timer <= 0) {
|
|
mic_timer = mic_sample_rate / 1000 * MIC_MAX_HOLD_TIME;
|
|
mic_max_display = mic_level;
|
|
mic_level = 1;
|
|
}
|
|
|
|
if ( ! tx_alc.buffer)
|
|
init_alc(&tx_alc, 960); // at 48000 sps, 960 is 20 msec
|
|
|
|
// quiskTxHoldState is a state machine to implement a pause for a repeater frequency shift for FM
|
|
key_down = quisk_is_key_down();
|
|
if (rxMode == FM || rxMode == DGT_FM) {
|
|
switch (quiskTxHoldState) {
|
|
case 0: // Never implement any hold
|
|
break;
|
|
case 1: // Start hold when key goes down
|
|
if (key_down)
|
|
quiskTxHoldState = 2;
|
|
break;
|
|
case 2: // Key down hold is in progress; wait until state changes to 3
|
|
break;
|
|
case 3: // Hold is released; when key goes up, hold starts again
|
|
if ( ! key_down)
|
|
quiskTxHoldState = 4;
|
|
break;
|
|
case 4: // Key up hold is in progress; wait until state changes to 1
|
|
break;
|
|
}
|
|
}
|
|
if (quiskTxHoldState == 2 || quiskTxHoldState == 4) { // don't transmit until the hold is cleared
|
|
key_down = 0;
|
|
for (i = 0; i < count; i++)
|
|
cSamples[i] = 0;
|
|
}
|
|
if (key_down != key_was_down) {
|
|
key_down_counter = 4800; // Key was pressed. Zero the first few samples to clear the buffers.
|
|
init_alc(&tx_alc, 0); // init ALC for Tx and RECORD_MIC
|
|
key_was_down = key_down;
|
|
}
|
|
if (key_down || DEBUG_MIC) { // create transmit I/Q samples
|
|
#if USE_GET_SIN == 2
|
|
transmit_udp(cSamples, count * interp);
|
|
#else
|
|
if (quiskSpotLevel >= 0) { // Spot is in use
|
|
count *= interp;
|
|
transmit_mic_carrier(cSamples, count, quiskSpotLevel / 1000.0);
|
|
}
|
|
else switch (rxMode) {
|
|
case LSB: // LSB
|
|
case USB: // USB
|
|
if (quisk_record_state == PLAYBACK)
|
|
count = tx_filter_digital(cSamples, count); // filter samples, minimal processing
|
|
else
|
|
count = tx_filter(cSamples, count); // filter samples
|
|
process_alc(cSamples, count, &tx_alc, rxMode);
|
|
break;
|
|
case AM: // AM
|
|
if (quisk_record_state != PLAYBACK) // no audio processing for recorded sound
|
|
count = tx_filter(cSamples, count);
|
|
for (i = 0; i < count; i++) // transmit (0.5 + ampl/2, 0)
|
|
cSamples[i] = (creal(cSamples[i]) + CLIP16) * 0.5;
|
|
process_alc(cSamples, count, &tx_alc, rxMode);
|
|
break;
|
|
case FM: // FM
|
|
if (quisk_record_state != PLAYBACK) // no audio processing for recorded sound
|
|
count = tx_filter(cSamples, count);
|
|
if (quisk_ctcss_freq > 9) {
|
|
ctcss_delta = 2.0 * M_PI / MIC_OUT_RATE * quisk_ctcss_freq;
|
|
ctcss_scale = 450.0 * modulation_index / quisk_ctcss_freq; // for 15% of total deviation
|
|
audio_scale = 0.85 * modulation_index / CLIP16;
|
|
for (i = 0; i < count; i++) {
|
|
ctcss_angle += ctcss_delta;
|
|
if (ctcss_angle >= 2.0 * M_PI)
|
|
ctcss_angle -= 2.0 * M_PI;
|
|
cSamples[i] = CLIP16 * cexp(I * (audio_scale * creal(cSamples[i]) + ctcss_scale * sin (ctcss_angle)));
|
|
}
|
|
}
|
|
else {
|
|
audio_scale = modulation_index / CLIP16;
|
|
for (i = 0; i < count; i++) // this is phase modulation == FM and 6 dB /octave preemphasis
|
|
cSamples[i] = CLIP16 * cexp(I * audio_scale * creal(cSamples[i]));
|
|
}
|
|
process_alc(cSamples, count, &tx_alc, rxMode);
|
|
break;
|
|
case DGT_U: // external digital modes
|
|
case DGT_L:
|
|
case DGT_IQ:
|
|
case DGT_FM:
|
|
count = tx_filter_digital(cSamples, count); // filter samples, minimal processing
|
|
process_alc(cSamples, count, &tx_alc, rxMode);
|
|
break;
|
|
case IMD: // transmit IMD 2-tone test
|
|
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;
|
|
}
|
|
#if DEBUG_IO || DEBUG
|
|
for (i = 0; i < count; i++) {
|
|
magn = cabs(cSamples[i]);
|
|
if (out_max < magn)
|
|
out_max = magn;
|
|
}
|
|
if (debug_timer == 0) {
|
|
printf("Max cSamples[] Output Level %9.6f\n", out_max / CLIP16);
|
|
out_max = 0;
|
|
}
|
|
#endif
|
|
if (key_down_counter > 0) { // zero the first few samples to clear the buffers.
|
|
for (i = 0; i < count && key_down_counter > 0; i++, key_down_counter--) {
|
|
if (key_down_counter < 480)
|
|
cSamples[i] *= (1.0 - key_down_counter / 480.0); // slow increase at end
|
|
else
|
|
cSamples[i] = 0; // initial samples are zero
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
if (reverse_tx_sideband)
|
|
for (i = 0; i < count; i++)
|
|
cSamples[i] = conj(cSamples[i]);
|
|
if(quisk_pt_sample_write) { // Used for SoapySDR
|
|
// Interpolate the mic samples to the Tx sample rate
|
|
//printf("Tx sample rate %i\n", tx_sample_rate);
|
|
switch (tx_sample_rate) {
|
|
case 100000:
|
|
count = quisk_cInterp2HB45(cSamples, count, &HalfBand);
|
|
// Fall through
|
|
case 50000:
|
|
if (! filt240D4.dCoefs)
|
|
quisk_filt_cInit(&filt240D4, quiskFilt240D4Coefs, sizeof(quiskFilt240D4Coefs)/sizeof(double));
|
|
if (! filt300D6.dCoefs)
|
|
quisk_filt_cInit(&filt300D6, quiskFilt300D6Coefs, sizeof(quiskFilt300D6Coefs)/sizeof(double));
|
|
count = quisk_cInterpDecim(cSamples, count, &filt240D4, 5, 4); // 60 kSps
|
|
count = quisk_cInterpDecim(cSamples, count, &filt300D6, 5, 6); // 50 kSps
|
|
break;
|
|
case 96000:
|
|
if (! filtInterp2.dCoefs)
|
|
quisk_filt_cInit(&filtInterp2, quiskFilt48dec24Coefs, sizeof(quiskFilt48dec24Coefs)/sizeof(double));
|
|
count = quisk_cInterpolate(cSamples, count, &filtInterp2, 2);
|
|
break;
|
|
case 192000:
|
|
if (! filtInterp2.dCoefs)
|
|
quisk_filt_cInit(&filtInterp2, quiskFilt48dec24Coefs, sizeof(quiskFilt48dec24Coefs)/sizeof(double));
|
|
count = quisk_cInterp2HB45(cSamples, count, &HalfBand);
|
|
count = quisk_cInterpolate(cSamples, count, &filtInterp2, 2);
|
|
break;
|
|
default: // 48000
|
|
break;
|
|
}
|
|
(*quisk_pt_sample_write)(cSamples, count);
|
|
}
|
|
else if (quisk_use_rx_udp == 10) { // Send Hermes mic samples when key is up or down
|
|
if ( ! quisk_rx_udp_started)
|
|
;
|
|
else {
|
|
if ((rxMode == CWL || rxMode == CWU) && quiskSpotLevel < 0) // CW and no Spot
|
|
for (i = 0; i < count; i++)
|
|
cSamples[i] = 0;
|
|
quisk_hermes_tx_add(cSamples, count, key_down);
|
|
}
|
|
}
|
|
else if (quisk_use_rx_udp && key_down) { // Send mic samples to UDP when key is down
|
|
transmit_udp(cSamples, count);
|
|
}
|
|
if (quisk_record_state == RECORD_MIC) {
|
|
switch (rxMode) {
|
|
case LSB: // LSB
|
|
case USB: // USB
|
|
count = tx_filter(cSamples, count); // filter samples
|
|
process_alc(cSamples, count, &tx_alc, rxMode);
|
|
break;
|
|
case AM: // AM
|
|
count = tx_filter(cSamples, count);
|
|
process_alc(cSamples, count, &tx_alc, rxMode);
|
|
break;
|
|
case FM: // FM
|
|
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;
|
|
break;
|
|
}
|
|
// Perhaps interpolate the mic samples back to the sound play rate
|
|
mic_interp = quisk_sound_state.playback_rate / MIC_OUT_RATE;
|
|
if (mic_interp > 1) {
|
|
if (! filtInterp.dCoefs)
|
|
quisk_filt_cInit(&filtInterp, quiskFilt12_19Coefs, sizeof(quiskFilt12_19Coefs)/sizeof(double));
|
|
count = quisk_cInterpolate(cSamples, count, &filtInterp, mic_interp);
|
|
}
|
|
quisk_tmp_record(cSamples, count, (double)CLIP32 / CLIP16); // convert 16 to 32 bits
|
|
}
|
|
|
|
#if DEBUG_IO || DEBUG
|
|
//QuiskPrintTime(" process_mic", 1);
|
|
#endif
|
|
return count;
|
|
}
|
|
|
|
PyObject * quisk_set_tx_audio(PyObject * self, PyObject * args, PyObject * keywds)
|
|
{ /* Call with keyword arguments ONLY; change Tx audio parameters */
|
|
static char * kwlist[] = {"vox_level", "vox_time", "mic_clip", "mic_preemphasis", "tx_sample_rate",
|
|
"reverse_tx_sideband", NULL} ;
|
|
int vlevel = -9999, clevel = -9999;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiidii", kwlist,
|
|
&vlevel, &timeVOX, &clevel, &quisk_mic_preemphasis, &tx_sample_rate, &reverse_tx_sideband))
|
|
return NULL;
|
|
if (vlevel != -9999)
|
|
vox_level = (int)(pow(10.0, vlevel / 20.0) * CLIP16); // Convert dB to 16-bit sample
|
|
if (clevel != -9999)
|
|
quisk_mic_clip = pow(10.0, clevel / 20.0); // Convert dB to factor
|
|
Py_INCREF (Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject * quisk_set_udp_tx_correct(PyObject * self, PyObject * args) // Called from GUI thread
|
|
{
|
|
double DcI, DcQ, level;
|
|
|
|
if (!PyArg_ParseTuple (args, "ddd", &DcI, &DcQ, &level))
|
|
return NULL;
|
|
if (DcI == 0 && DcQ == 0 && level == 1.0){
|
|
doTxCorrect = 0;
|
|
}
|
|
else {
|
|
doTxCorrect = 1;
|
|
TxCorrectDc = (DcI + I * DcQ) * CLIP16;
|
|
DcI = fabs(DcI);
|
|
DcQ = fabs(DcQ);
|
|
if (DcI > DcQ)
|
|
TxCorrectLevel = 1.0 - DcI;
|
|
else
|
|
TxCorrectLevel = 1.0 - DcQ;
|
|
TxCorrectLevel *= level;
|
|
}
|
|
Py_INCREF (Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject * quisk_is_vox(PyObject * self, PyObject * args)
|
|
{ /* return the VOX state */
|
|
if (!PyArg_ParseTuple (args, ""))
|
|
return NULL;
|
|
return PyInt_FromLong(is_vox);
|
|
}
|
|
|
|
PyObject * quisk_set_hermes_filter(PyObject * self, PyObject * args)
|
|
{
|
|
if (!PyArg_ParseTuple (args, "ii", &hermes_filter_rx, &hermes_filter_tx))
|
|
return NULL;
|
|
Py_INCREF (Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject * quisk_set_alex_hpf(PyObject * self, PyObject * args)
|
|
{
|
|
if (!PyArg_ParseTuple (args, "ii", &alex_hpf_rx, &alex_hpf_tx))
|
|
return NULL;
|
|
Py_INCREF (Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
PyObject * quisk_set_alex_lpf(PyObject * self, PyObject * args)
|
|
{
|
|
if (!PyArg_ParseTuple (args, "ii", &alex_lpf_rx, &alex_lpf_tx))
|
|
return NULL;
|
|
Py_INCREF (Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
void quisk_close_mic(void)
|
|
{
|
|
if (mic_socket != INVALID_SOCKET) {
|
|
close(mic_socket);
|
|
mic_socket = INVALID_SOCKET;
|
|
}
|
|
#ifdef MS_WINDOWS
|
|
if (mic_cleanup)
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
void quisk_open_mic(void)
|
|
{
|
|
struct sockaddr_in Addr;
|
|
int sndsize = 48000;
|
|
#if DEBUG_IO || DEBUG
|
|
int intbuf;
|
|
#ifdef MS_WINDOWS
|
|
int bufsize = sizeof(int);
|
|
#else
|
|
socklen_t bufsize = sizeof(int);
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MS_WINDOWS
|
|
WORD wVersionRequested;
|
|
WSADATA wsaData;
|
|
#endif
|
|
|
|
modulation_index = QuiskGetConfigDouble("modulation_index", 1.6);
|
|
mic_agc_level = QuiskGetConfigDouble("mic_agc_level", 0.1);
|
|
if (quisk_sound_state.tx_audio_port == 0x553B)
|
|
align4 = 0; // Using old port: data starts at byte 42.
|
|
else
|
|
align4 = 1; // Start data at byte 44; align to dword
|
|
if (quisk_sound_state.mic_ip[0]) {
|
|
#ifdef MS_WINDOWS
|
|
wVersionRequested = MAKEWORD(2, 2);
|
|
if (WSAStartup(wVersionRequested, &wsaData) != 0)
|
|
return; // failure to start winsock
|
|
mic_cleanup = 1;
|
|
#endif
|
|
mic_socket = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if (mic_socket != INVALID_SOCKET) {
|
|
setsockopt(mic_socket, SOL_SOCKET, SO_SNDBUF, (char *)&sndsize, sizeof(sndsize));
|
|
Addr.sin_family = AF_INET;
|
|
// This is the UDP port for TX microphone samples, and must agree with the microcontroller.
|
|
Addr.sin_port = htons(quisk_sound_state.tx_audio_port);
|
|
#ifdef MS_WINDOWS
|
|
Addr.sin_addr.S_un.S_addr = inet_addr(quisk_sound_state.mic_ip);
|
|
#else
|
|
inet_aton(quisk_sound_state.mic_ip, &Addr.sin_addr);
|
|
#endif
|
|
if (connect(mic_socket, (const struct sockaddr *)&Addr, sizeof(Addr)) != 0) {
|
|
close(mic_socket);
|
|
mic_socket = INVALID_SOCKET;
|
|
}
|
|
else {
|
|
#if DEBUG_IO || DEBUG
|
|
if (getsockopt(mic_socket, SOL_SOCKET, SO_SNDBUF, (char *)&intbuf, &bufsize) == 0)
|
|
printf("UDP mic socket send buffer size %d\n", intbuf);
|
|
else
|
|
printf ("Failure SO_SNDBUF\n");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|