OMG, did someone say that EbNaut is difficult??
Orders of magnitude of complexity seem to be out there..
73, Stefan
Am 29.12.2018 21:54, schrieb Wolfgang Büscher:
Below: Core of the "universal frequency converter / mixer" as currently
used in Spectrum Lab (this is the simple one, without forward and
reverse FFT).
void SOUND_RunThroughMixer(
T_SOUND_FREQ_CONVERTER *pcnv, // contains all parameters for a
converter
T_Float *input_samples, T_Float *input_samples_q,
T_Float *output_samples, T_Float *output_samples_q,
int number_of_samples )
/* Frequency mixer. May transform the frequency for some kind of RX.
* Can optionally cancel one sideband using a Hilbert transformer,
* I/Q-mixer etc.
*/
{
int sample_i, j;
T_Float d, y, dbl_i, dbl_q;
const T_Float* coeff_ptr;
T_Float* queue_ptr;
T_Float* input_queue_end;
double dblPhzInc;
int iCosTableIndex, iSineTableOffset = 0;
T_Float dblNcoI, dblNcoQ;
double dblNcoPhase = pcnv->dblNcoPhase; // use local var for speed
// Prepare the NCO for the "mixer" frequency ...
// Note: SoundTab_fltCosTable[SOUND_COS_TABLE_LEN] contains exactly
ONE PERIOD.
// "Negative" frequencies are possible by inverting the
Q-channel .
if(pcnv->dblFSample!=0)
dblPhzInc = (double)SOUND_COS_TABLE_LEN *
pcnv->dblOscillatorFreq / pcnv->dblFSample;
else
dblPhzInc = 0; // mixer frequency is ZERO, so noo phase
increment
if(dblPhzInc > 0)
{ // array index offset to read a SINE WAVE from a COSINE TABLE :
iSineTableOffset = (int) (0.499 + 3.0 *
(T_Float)SOUND_COS_TABLE_LEN / 4.0);
}
else // phase increment negative, "negative" mixer frequency ....
{
dblPhzInc = -dblPhzInc; // Make sure the table reading index
INCREMENTS,
// because the wrap test will not look for
negative indices.
// Get array index offset to read a NEGATIVE SINE WAVE from a
COSINE TABLE :
iSineTableOffset = (int)(0.499 + (T_Float)SOUND_COS_TABLE_LEN /
4.0);
} // end if <produce NEGATIVE frequencies ... possible only with
I/Q mult. >
if(dblNcoPhase<0) dblNcoPhase = 0;
if(pcnv->fDcReject)
{ // Accumulate & remove a bit DC offset from the new chunk:
for(sample_i=0; sample_i<number_of_samples; ++sample_i)
{
pcnv->dblMixerDcOffset =
pcnv->dblMixerDcOffset * 0.9999
+ input_samples[sample_i] * 0.0001;
// bad style to modifying the INPUT samples
// but this won't hurt anyone :>
input_samples[sample_i] -= pcnv->dblMixerDcOffset;
}
}
if(pcnv->iMixerScheme==CFG_FREQ_MIX_DSB)
{ // No sideband rejection:
// This is a simple and very fast mixer without
sideband-rejection.
// No complicated broadband 90° phase shifter required.
for(sample_i=0; sample_i<number_of_samples; ++sample_i)
{
// Run the NCO (numerical controlled oscillator)
dblNcoPhase += dblPhzInc;
while(dblNcoPhase >=(T_Float)SOUND_COS_TABLE_LEN) // "while",
not "if" !!
dblNcoPhase -=(T_Float)SOUND_COS_TABLE_LEN; // table index
wrap
d = SoundTab_fltCosTable[(int)dblNcoPhase];
// DSB mixer: simply multiply the input signal with the NCO
output :
*output_samples++ = (*input_samples++) * d;
}
} // end if <frequency converter WITHOUT sideband rejection>
else
if(pcnv->iMixerScheme==CFG_FREQ_MIX_COMPLEX)
{ // Complex multiplication of complex input :
// (a + j*b ) * ( c + j*d ) = a*c - b*d + j * ( a*d + b*c )
// To do this, SOUND_RunThroughMixer() must be called with
// TWO source- and TWO destination blocks.
// (real and imaginary parts are in sepearate blocks)
if( (input_samples!=NULL) && (input_samples_q!=NULL)
&&(output_samples!=NULL) &&
(output_samples_q!=NULL) )
{
T_Float inp_i, inp_q, outp_i, outp_q;
for(sample_i=0; sample_i<number_of_samples; ++sample_i)
{
// Run the NCO (numerical controlled oscillator; here with
quadrature output)
dblNcoPhase += dblPhzInc;
while(dblNcoPhase >=(T_Float)SOUND_COS_TABLE_LEN) //
"while", not "if" !!
dblNcoPhase -=(T_Float)SOUND_COS_TABLE_LEN; // table
index wrap
iCosTableIndex = (int)dblNcoPhase;
dblNcoI = SoundTab_fltCosTable[iCosTableIndex];
dblNcoQ =
SoundTab_fltCosTable[(iCosTableIndex+iSineTableOffset) %
SOUND_COS_TABLE_LEN];
// Get complex input sample, multiply with complex
oscillator, and put into output buffers:
inp_i = *(input_samples++);
inp_q = *(input_samples_q++);
outp_i = inp_i * dblNcoI - inp_q * dblNcoQ;
outp_q = inp_i * dblNcoQ + inp_q * dblNcoI;
*output_samples++ = outp_i;
*output_samples_q++ = outp_q;
}
}
} // end if <frequency converter WITHOUT sideband rejection>
else
if( (pcnv->iMixerScheme == CFG_FREQ_MIX_LSB_DOWNCONVERTER )
|| (pcnv->iMixerScheme == CFG_FREQ_MIX_USB_DOWNCONVERTER ) )
{ // Optimized mixer for "downconversion".
// Based on a schematic diagram from the ARRL handbook 1996,
// page 17.73: "The R2: An image-rejecting D-C Receiver"
// Principle: Splitter, TWO Mixers (with I and Q output),
// audio phase shift network, summer, filter.
// Note: 90° phase shifting is done AFTER mixing (in contrast to
the
// schemes CFG_FREQ_MIX_LSB_UP and
CFG_FREQ_MIX_USB_UP.
for(sample_i=0; sample_i<number_of_samples; ++sample_i)
{
// Let the NCO (numerical controlled oscillator) produce
quadrature-phase signals :
dblNcoPhase += dblPhzInc;
while(dblNcoPhase >=(T_Float)SOUND_COS_TABLE_LEN) // "while",
not "if" !!
dblNcoPhase -=(T_Float)SOUND_COS_TABLE_LEN; // table index
wrap
iCosTableIndex = (int)dblNcoPhase;
dblNcoI = SoundTab_fltCosTable[iCosTableIndex];
dblNcoQ = SoundTab_fltCosTable[(iCosTableIndex+iSineTableOffset)
% SOUND_COS_TABLE_LEN];
// Now mix the incoming (higher-frequency) signal
..input_samples[]..
// with a sine and a cosine signal from the NCO ("LO").
dbl_i = input_samples[sample_i]; // this is the INPUT signal
if(pcnv->iMixerScheme == CFG_FREQ_MIX_LSB_DOWNCONVERTER)
{
dbl_q = dbl_i * dblNcoI; // LSB (below NCO freq) moved DOWN
dbl_i = dbl_i * dblNcoQ;
}
else // the "other" sideband with reversed oscillator phases:
{
dbl_q = dbl_i * dblNcoQ; // USB (above NCO freq) moved DOWN
dbl_i = dbl_i * dblNcoI;
}
// arrived here: dbl_i is the output of the "I"-mixer,
// dbl_q is the output of the "Q"-mixer.
// Let the I-channel run through a delay line [ length (N-1)/2 ]
// to compensate the delay from the hilbert trafo (see below).
if( (pcnv->iDelayQIndex < 0)
||(pcnv->iDelayQIndex >=
((SOUND_Hilbert_filter_length-1)/2)) )
pcnv->iDelayQIndex = 0;
y = pcnv->dblDelayQueue[pcnv->iDelayQIndex];
pcnv->dblDelayQueue[pcnv->iDelayQIndex++] = dbl_i;
dbl_i = y;
// Let the Q-channel run through a broadband 90° phase shifter
// (hilbert transformer, implemented as FIR filter here).
// keep the circular buffer pointer 'valid' all the time :
input_queue_end =
&pcnv->dblHilbertQueue[SOUND_Hilbert_filter_length-1];
if( (pcnv->pdblHilbertQPointer <
&pcnv->dblHilbertQueue[0])
||(pcnv->pdblHilbertQPointer > input_queue_end) )
pcnv->pdblHilbertQPointer =
&pcnv->dblHilbertQueue[0];
if( --pcnv->pdblHilbertQPointer <
&pcnv->dblHilbertQueue[0] )
pcnv->pdblHilbertQPointer = input_queue_end; // deal
with wraparound
*pcnv->pdblHilbertQPointer = dbl_q; // hilbert trafo input
queue_ptr = pcnv->pdblHilbertQPointer; // pointer to read
from input queue
coeff_ptr = &SOUND_HilbertCoeffs[0]; // pointer to read from
coeff table
y = 0.0; // clear the 'global adder'
j = (SOUND_Hilbert_filter_length+1)/2 -1; // j=12 for 25th-order
filter
while( j-- ) // do the MAC's (with every 2nd coeff
ZERO)
{
// coeffs[0], [2], [4].. are (almost) zero and are not
calculated
// so just skip the 'queue' pointer
++queue_ptr; ++coeff_ptr;
if( queue_ptr > input_queue_end ) // deal with wraparound
queue_ptr = &pcnv->dblHilbertQueue[0];
// odd coefficients, non-zero, do a MAC-operation :
y += ( (*queue_ptr++) * (*coeff_ptr++) );
if( queue_ptr > input_queue_end ) // deal with wraparound
queue_ptr = &pcnv->dblHilbertQueue[0];
}
dbl_q = y; // filter output = sum from all 'taps'
// finally add the output of the "audio phase-shift network"...
output_samples[sample_i] = dbl_i + dbl_q;
} // end for (sample_i ... )
} // end if <..mixer_scheme == CFG_FREQ_MIX_USB_DOWNCONVERTER >
else // neither "double side band" nor "USB DOWNCONVERTER"....
{
for(sample_i=0; sample_i<number_of_samples; ++sample_i)
{
// Let the I-value run through a delay line [ length (N-1)/2 ]
// to compensate the delay from the hilbert trafo (see below).
if( (pcnv->iDelayQIndex < 0)
||(pcnv->iDelayQIndex >=
((SOUND_Hilbert_filter_length-1)/2)) )
pcnv->iDelayQIndex = 0;
dbl_i = pcnv->dblDelayQueue[pcnv->iDelayQIndex];
pcnv->dblDelayQueue[pcnv->iDelayQIndex++]
= input_samples[sample_i];
// Generate the Q-value by passing the real input through a
// hilbert transformer (which is implemented as an FIR filter
here).
// keep the circular buffer pointer 'valid' all the time :
input_queue_end =
&pcnv->dblHilbertQueue[SOUND_Hilbert_filter_length-1];
if( (pcnv->pdblHilbertQPointer <
&pcnv->dblHilbertQueue[0])
||(pcnv->pdblHilbertQPointer > input_queue_end) )
pcnv->pdblHilbertQPointer =
&pcnv->dblHilbertQueue[0];
if( --pcnv->pdblHilbertQPointer <
&pcnv->dblHilbertQueue[0] )
pcnv->pdblHilbertQPointer = input_queue_end; // deal
with wraparound
*pcnv->pdblHilbertQPointer = input_samples[sample_i]; //
filter input = "X"
queue_ptr = pcnv->pdblHilbertQPointer; // pointer to read
from input queue
coeff_ptr = &SOUND_HilbertCoeffs[0]; // pointer to read from
coeff table
y = 0.0; // clear the 'global adder'
j = (SOUND_Hilbert_filter_length+1)/2 -1; // j=12 for 25th-order
filter
while( j-- ) // do the MAC's (with every 2nd coeff
ZERO)
{
// coeffs[0], [2], [4].. are (almost) zero and are not
calculated
// so just skip the 'queue' pointer
#if(0) // not optimized:
y += ( (*queue_ptr++) * (*coeff_ptr++) ); // here's the MAC
#else // optimized:
++queue_ptr; ++coeff_ptr;
#endif
if( queue_ptr > input_queue_end ) // deal with wraparound
queue_ptr = &pcnv->dblHilbertQueue[0];
// odd coefficients, non-zero, do a MAC-operation :
y += ( (*queue_ptr++) * (*coeff_ptr++) );
if( queue_ptr > input_queue_end ) // deal with wraparound
queue_ptr = &pcnv->dblHilbertQueue[0];
}
dbl_q = y; // filter output = sum from all 'taps'
// Let the NCO (numerical controlled oscillator) produce
quadrature-phase signals :
dblNcoPhase += dblPhzInc;
while(dblNcoPhase >=(T_Float)SOUND_COS_TABLE_LEN) // "while",
not "if" !!
dblNcoPhase -=(T_Float)SOUND_COS_TABLE_LEN; // table index
wrap
iCosTableIndex = (int)dblNcoPhase;
dblNcoI = SoundTab_fltCosTable[iCosTableIndex];
dblNcoQ = SoundTab_fltCosTable[(iCosTableIndex +
iSineTableOffset) % SOUND_COS_TABLE_LEN];
if(pcnv->iMixerScheme==CFG_FREQ_MIX_LSB_UP)
{
// multiply the I- and Q- samples with the sin- and cos- output
// of the local oscillator.
output_samples[sample_i] =
dbl_q * dblNcoI // multiply with "cosine"
- dbl_i * dblNcoQ; // multiply with "sine"
}
else // not LSB but USB:
{
output_samples[sample_i] =
dbl_i * dblNcoI // multiply with "cosine"
- dbl_q * dblNcoQ; // multiply with "sine"
}
} // end for (sample_i ... )
} // end if <frequency UP converter with sideband rejection>
pcnv->dblNcoPhase = dblNcoPhase; // save the NCO phase for next
block
} // end SOUND_RunThroughMixer()
On 29.12.2018 10:47, Rik Strobbe wrote:
Hello Markus,
nice copy on N1BUG, this might be a new distance record for Paul.
Thanks for your help with the JT9-2 and JT9-5 resampling.
Lifting the frequency restriction on TX is "peanuts", but on RX
I would need to implement frequency conversion in SpecLab style.
I will have a look to that (maybe I should ask Wolf for advice).
73, Rik ON7YD - OR7T
Thanks to Rik for the software, and to Spiros and Dionysios for their
reports. This is what I picked up using JT9-2
18-12-28 21:38 2038 -25 0.1 648 2 CQ SV8CS KM07
18-12-28 21:40 2040 -25 0.1 1200 2 LA3EQ JO28XJ
18-12-28 21:42 2042 -26 0.1 648 2 CQ SV8CS KM07
18-12-28 21:44 2044 -26 0.2 1200 2 LA3EQ JO28XJ
18-12-28 21:48 2048 -25 0.1 1250 2 LA3EQ JO28XJ
18-12-28 21:52 2052 -25 0.2 1250 2 LA3EQ JO28XJ
18-12-28 21:56 2056 -25 0.2 1250 2 LA3EQ JO28XJ
18-12-28 22:00 2100 -28 0.3 1250 2 LA3EQ JO28XJ
and later JT9-5:
18-12-29 02:30 0130 -34 -0.7 615 5 VVV N1BUG 5
18-12-29 02:40 0140 -34 -0.6 615 5 VVV N1BUG 5
18-12-29 03:30 0230 -35 -0.6 615 5 VVV N1BUG 5
As my LF transceiver needs to be parked on 135.5 kHz I am always
struggling with audio frequency restrictions in WSJT, WSPR and JT-9
software. My workaround are frequency conversions in two separate
SpecLab instances for RX and TX, connected to the left and right
channels of a single VB-Audio virtual cable instance. However there is
a noticable latency in this process.
Best 73,
Markus (DF6NM)
-----Ursprüngliche Mitteilung-----
Von: Dionysios Vlachiotis <[email protected]>
An: rsgb_lf_group <[email protected]>;
rsgb_lf_group <[email protected]>
Verschickt: Sa, 29. Dez 2018 8:39
Betreff: LF: JT9-5 decodes in km07ks
245 -28 -0.4 650 5 CQ DF6NM JN59
2255 -27 -0.4 650 5 CQ DF6NM JN59
2315 -27 -0.5 650 5 CQ DF6NM JN59
2325 -24 -0.3 650 5 CQ DF6NM JN59
0035 -23 -0.2 650 5 CQ DF6NM JN59
0045 -21 -0.3 650 5 CQ DF6NM JN59
73 de SV8RV
Zakynthos isl. GREECE (km07ks)
-----Ursprüngliche Mitteilung-----
Von: SV8CS-Spiros Chimarios <[email protected]>
An: rsgb_lf_group <[email protected]>
Verschickt: Sa, 29. Dez 2018 5:56
Betreff: LF: SV8CS SlowJT9
Good morning.
Decoded only DF6NM during the night (JT9-5) – 136 KHz.
2245 -30 -0.4 650 5 CQ DF6NM JN59
2255 -29 -0.4 650 5 CQ DF6NM JN59
2315 -27 -0.4 650 5 CQ DF6NM JN59
2325 -26 -0.4 650 5 CQ DF6NM JN59
0035 -21 -0.6 650 5 CQ DF6NM JN59
0045 -20 -0.4 650 5 CQ DF6NM JN59
73, Spiros/SV8CS
|
|