--- /dev/null
+/*
+ * Contribution of DL1YCF Christoph van W"ullen:
+ *
+ * This is the "ramp" with length 200 which is
+ * the step response derived from a digital filter
+ * with a Blackman-Harris window.
+ * It is used to shape the rising and falling
+ * edge of a CW pulse. The goal is to have
+ * a pulse whose off-center frequency components
+ * quickly fall off.
+ *
+ * The Blackman-Harris window function is
+ *
+ * B(x) = a0 - a1 Cos(2 Pi x) + a2 Cos(4 Pi x) - a3 Cos(6 Pi x)
+ *
+ * with coefficients
+ * a0 = 0.35875
+ * a1 = 0.48829
+ * a2 = 0.14128
+ * a3 = 0.01168
+ *
+ * And the ramp of length 200 is
+ *
+ * cwramp[i] = (Integral of B(x) from zero to i/200) / a0
+ *
+ * where a0 is (Integral of (Bx) from zero to one)
+ *
+ * such that cwramp[0] = cwramp[200] = 0, and the values rise
+ * with the shape of a sigmoid.
+ *
+ * The values here are calculated using MATHEMATICA with 32-digit accuracy
+ * (more than we need for double) and copy/pasted into this file.
+ *
+ *
+ * MEASURING SPECTRAL POLLUTION
+ * ============================
+ *
+ * Using an SDR, a DummyLoad/Attenuator and my Kenwood TRX for receiving
+ * the signal, I started a 20 wpm CQ call on the SDR at 7010 kHz and
+ * monitored the S-meter of my Kenwood rig for different frequencies.
+ * I used a 60dB attenuator which made a S9+50dB signal in my Kenwood
+ * out of the 10 Watts output of my SDR.
+ * Three cases were measured:
+ * a) "hard" CW signal, rectangular CW pulses
+ * b) CW signal with linear ramps, width 4 msec
+ * c) CW signal with Blackman-Harris ramp (as defined here), width 4 msec
+ *
+ * These are the results:
+ *
+ * Freq / kHz S-Meter S-Meter S-Meter
+ * "hard" "linear" "Blackman-Harris"
+ * ------------------------------------------------------
+ * 7010.0 S9+50 S9+50 S9+50
+ * 7009.5 S9+25 S9 S9+10
+ * 7009.0 S9+20 S6 S0
+ * 7008.5 S9+15 S4 S0
+ * 7008.0 S9+10 S3 S0
+ * 7005.0 S9+5 S0 S0
+ * 7000.0 S8 S0 S0
+ * -----------------------------------------------------
+ *
+ * (for frequencies above 7010 kHz, one gets mirror-image results).
+ *
+ * The linear ramp is a big improvement, but the Blackman-Harris
+ * ramp does far better if more than 500 Hz off.
+ */
+
+double cwramp[201] = {
+0.0,
+9.01187795859706156177643839E-7,
+2.194323834526231706516646770E-6,
+4.282571576728779171701009811E-6,
+7.59152417258436512341254065E-6,
+0.00001258041690211947993636267109,
+0.00001975333523137922572945668287,
+0.00002967041454480472948228913986,
+0.00004295902553278649886291894300,
+0.00006032493666320024489466741405,
+0.00008256344218126450788500686944,
+0.00011057044070320856501019611699,
+0.00014535344574159831560164368292,
+0.00018804250547447493441427381117,
+0.00023990100480207670388152058280,
+0.00030233631828325189492558643061,
+0.00037691027797158203918335035555,
+0.00046534941554435985240942810166,
+0.00056955493350364783431057938016,
+0.00069161235569682109078184134293,
+0.00083380080302407864906440039504,
+0.00099860183604212891204121277035,
+0.00118870780230554127073025982217,
+0.00140702962277747793984056753115,
+0.00165670394855476385194253550184,
+0.00194109961655060504615626150874,
+0.00226382333072012476435261017988,
+0.00262872449395329839902468237813,
+0.00303989911494590614436084016036,
+0.00350169271423530270458824302775,
+0.00401870215419155535384463676037,
+0.00459577631911668629751740793823,
+0.00523801557374924620039546203446,
+0.00595076993141477129560262502588,
+0.00673963586681372911811349735623,
+0.00761045171299837322070433116670,
+0.00856929158745153542728726050674,
+0.00962245779832873572690245854193,
+0.01077647168883695665617177556310,
+0.01203806288536788123878808156746,
+0.01341415692334135279803139842992,
+0.01491186123369968180032171505537,
+0.01653844948257128001833036523305,
+0.01830134426673207878624266925140,
+0.02020809817806791189569866340858,
+0.02226637326120713880733998238444,
+0.02448391889977143838668571452861,
+0.02686854817820029119729513109423,
+0.02942811277775342177744558204085,
+0.03217047647699419958395416072235,
+0.03510348733871183926970109538183,
+0.03823494867675546007436492565069,
+0.04157258890753185887801606599357,
+0.04512403040186418964004939087610,
+0.04889675746342420481031099370853,
+0.05289808356994134481892840233434,
+0.05713511802276510901007293628661,
+0.06161473215902329384830063982633,
+0.06634352528849228438584699015456,
+0.07132779052429581807377516557786,
+0.07657348068260018970052026096202,
+0.08208617443150763044908273672262,
+0.0878710428733033542817043103120,
+0.0939328167470308029827954611885,
+0.1002757544400072779860624428609,
+0.1069036109973103171592225098772,
+0.1138196083174347330899436336723,
+0.1210264067202213859669792420239,
+0.1285260780697813419156364133806,
+0.1363200806304827024531349966765,
+0.1444092358281416498341153348924,
+0.1527937070813836713066362714703,
+0.1614729808597449580170472173813,
+0.1704458501155068668214820716660,
+0.1797104002255469077569583767922,
+0.1892639975677060809000157790299,
+0.1991032808433815468445483021142,
+0.2092241552443310463622979568190,
+0.2196217895471045931434166370431,
+0.2302906162031904975140892412129,
+0.2412243344769741731293350985271,
+0.2524159166670628372463994300812,
+0.2638576174295357435495515307263,
+0.2755409862043509745237860450997,
+0.2874568827285925860110134866717,
+0.2995954956025951958334437651411,
+0.3119463638573578192626567029370,
+0.3244984014541765421903072764578,
+0.3372399246302079959411341784319,
+0.3501586819868429577753048512722,
+0.3632418872014401014103738694015,
+0.3764762542272593365183481179365,
+0.3898480348314537908663795438645,
+0.4033430583068360197915402597148,
+0.4169467731799285763401037295609,
+0.4306442907256363254915247826210,
+0.4444204300878253507887486917206,
+0.4582597647952406272219259607917,
+0.4721466704536129633322770005995,
+0.4860653733875571201965669425722,
+0.5000000000000000000000000000000,
+0.5139346266124428798034330574278,
+0.5278533295463870366677229994005,
+0.5417402352047593727780740392083,
+0.5555795699121746492112513082794,
+0.5693557092743636745084752173790,
+0.5830532268200714236598962704391,
+0.5966569416931639802084597402852,
+0.6101519651685462091336204561355,
+0.6235237457727406634816518820635,
+0.6367581127985598985896261305985,
+0.6498413180131570422246951487278,
+0.6627600753697920040588658215681,
+0.6755015985458234578096927235422,
+0.6880536361426421807373432970630,
+0.7004045043974048041665562348589,
+0.7125431172714074139889865133283,
+0.7244590137956490254762139549003,
+0.7361423825704642564504484692737,
+0.7475840833329371627536005699188,
+0.7587756655230258268706649014729,
+0.7697093837968095024859107587871,
+0.7803782104528954068565833629569,
+0.7907758447556689536377020431810,
+0.8008967191566184531554516978858,
+0.8107360024322939190999842209701,
+0.8202895997744530922430416232078,
+0.8295541498844931331785179283340,
+0.8385270191402550419829527826187,
+0.8472062929186163286933637285297,
+0.8555907641718583501658846651076,
+0.8636799193695172975468650033235,
+0.8714739219302186580843635866194,
+0.8789735932797786140330207579761,
+0.8861803916825652669100563663277,
+0.8930963890026896828407774901228,
+0.8997242455599927220139375571391,
+0.9060671832529691970172045388115,
+0.9121289571266966457182956896880,
+0.9179138255684923695509172632774,
+0.9234265193173998102994797390380,
+0.9286722094757041819262248344221,
+0.9336564747115077156141530098454,
+0.9383852678409767061516993601737,
+0.9428648819772348909899270637134,
+0.9471019164300586551810715976657,
+0.9511032425365757951896890062915,
+0.9548759695981358103599506091239,
+0.9584274110924681411219839340064,
+0.9617650513232445399256350743493,
+0.9648965126612881607302989046182,
+0.9678295235230058004160458392777,
+0.9705718872222465782225544179592,
+0.9731314518217997088027048689058,
+0.9755160811002285616133142854714,
+0.9777336267387928611926600176156,
+0.9797919018219320881043013365914,
+0.9816986557332679212137573307486,
+0.9834615505174287199816696347670,
+0.9850881387663003181996782849446,
+0.9865858430766586472019686015701,
+0.9879619371146321187612119184325,
+0.9892235283111630433438282244369,
+0.9903775422016712642730975414581,
+0.9914307084125484645727127394933,
+0.9923895482870016267792956688333,
+0.9932603641331862708818865026438,
+0.9940492300685852287043973749741,
+0.9947619844262507537996045379655,
+0.9954042236808833137024825920618,
+0.9959812978458084446461553632396,
+0.9964983072857646972954117569722,
+0.9969601008850540938556391598396,
+0.9973712755060467016009753176219,
+0.9977361766692798752356473898201,
+0.9980589003834493949538437384913,
+0.9983432960514452361480574644982,
+0.9985929703772225220601594324689,
+0.9988112921976944587292697401778,
+0.9990013981639578710879587872296,
+0.9991661991969759213509355996050,
+0.9993083876443031789092181586571,
+0.9994304450664963521656894206198,
+0.9995346505844556401475905718983,
+0.9996230897220284179608166496444,
+0.9996976636817167481050744135694,
+0.9997600989951979232961184794172,
+0.9998119574945255250655857261888,
+0.9998546465542584016843983563171,
+0.9998894295592967914349898038830,
+0.9999174365578187354921149931306,
+0.9999396750633367997551053325860,
+0.9999570409744672135011370810570,
+0.9999703295854551952705177108601,
+0.9999802466647686207742705433171,
+0.9999874195830978805200636373289,
+0.9999924084758274156348765874593,
+0.9999957174284232712208282989902,
+0.9999978056761654737682934833532,
+0.9999990988122041402938438223562,
+1.0000000000000000000000000000000};
+
int cw_not_ready=1;
// cw_shape_buffer will eventually be integrated into TRANSMITTER
-static int *cw_shape_buffer = NULL;
+static double *cw_shape_buffer = NULL;
static int cw_shape = 0;
+// cwramp is the function defining the "ramp" of the CW pulse.
+// is *must be* an array with 201 entries. The ramp width (200 samples)
+// is hard-coded.
+extern double cwramp[]; // see cwramp.c
extern void cw_audio_write(double sample);
tx->samples=0;
tx->pixel_samples=malloc(sizeof(float)*tx->pixels);
if (cw_shape_buffer) free(cw_shape_buffer);
- cw_shape_buffer=malloc(sizeof(int)*tx->buffer_size);
+ cw_shape_buffer=malloc(sizeof(double)*tx->buffer_size);
fprintf(stderr,"transmitter: allocate buffers: mic_input_buffer=%p iq_output_buffer=%p pixels=%p\n",tx->mic_input_buffer,tx->iq_output_buffer,tx->pixel_samples);
fprintf(stderr,"create_transmitter: OpenChannel id=%d buffer_size=%d fft_size=%d sample_rate=%d dspRate=%d outputRate=%d\n",
static void full_tx_buffer(TRANSMITTER *tx) {
long isample;
long qsample;
- double gain,fgain,sidevol;
+ double gain, sidevol, ramp, fgain;
int j;
int error;
int mode;
if ((mode == modeCWL || mode == modeCWU) && !tune) {
//
// "pulse shape case":
- // shape the I/Q samples with the envelope function stored in cw_shape_buffer
- // and produce side tone (again with shaped pulses)
+ // shape the I/Q samples with the envelope stored in cw_shape_buffer.
+ // We also produce a side tone with same shape.
//
- fgain=gain*0.005; // will be multiplied with cw_shape
- sidevol= 1.29 * cw_keyer_sidetone_volume; // will be multiplied with cw_shape
+ fgain=gain; // will be multiplied with ramp function
+ sidevol= 258.0 * cw_keyer_sidetone_volume; // will be multiplied with ramp function
for(j=0;j<tx->output_samples;j++) {
- gain=fgain*cw_shape_buffer[j];
+ ramp=cw_shape_buffer[j]; // between 0 and 1
+ gain=fgain*ramp;
double is=tx->iq_output_buffer[j*2];
double qs=tx->iq_output_buffer[(j*2)+1];
isample=is>=0.0?(long)floor(is*gain+0.5):(long)ceil(is*gain-0.5);
// since we may use getNextSidetoneSample for local audio, we need
// an independent instance thereof here. To be nice to the CW
// operator, the audio is shaped the same way as the RF
- sidetone=sidevol * cw_shape_buffer[j] * getNextInternalSideToneSample();
+ sidetone=sidevol * ramp * getNextInternalSideToneSample();
old_protocol_iq_samples_with_sidetone(isample,qsample,sidetone);
break;
case NEW_PROTOCOL:
void add_mic_sample(TRANSMITTER *tx,short mic_sample) {
int mode;
double sample;
- double mic_sample_double;
+ double mic_sample_double, ramp;
int i,s;
if(split) {
// cw_key_down can be zero, for inserting some space
//
// We HAVE TO shape the signal to avoid hard clicks to be
-// heard way beside our frequency. The envelope goes up
-// and down linearly within 200 samples (4.16 msec)
+// heard way beside our frequency. The envelope (ramp function)
+// is stored in cwramp[0::200], so we "move" cw_shape between these
+// values. The ramp width is 200 samples (4.16 msec)
+//
+// Note that usually, the pulse is much broader than the ramp,
+// that is, cw_key_down and cw_key_up are much larger than 200.
//
cw_not_ready=0;
if (cw_key_down > 0 ) {
- if (cw_shape < 200) cw_shape++;
- cw_key_down--;
+ if (cw_shape < 200) cw_shape++; // walk up the ramp
+ cw_key_down--; // decrement key-up counter
} else {
if (cw_key_up >= 0) {
// dig into this even if cw_key_up is already zero, to ensure
- // that cw_shape eventually reaches zero
- if (cw_shape > 0) cw_shape--;
- if (cw_key_up > 0) cw_key_up--;
+ // that we reach the bottom of the ramp for very small pauses
+ if (cw_shape > 0) cw_shape--; // walk down the ramp
+ if (cw_key_up > 0) cw_key_up--; // decrement key-down counter
}
}
- cw_audio_write(0.00003937 * getNextSideToneSample() * cw_keyer_sidetone_volume * cw_shape);
- cw_shape_buffer[tx->samples]=cw_shape;
+ // store the ramp value in cw_shape_buffer, but also use it for shaping the "local"
+ // side tone
+ ramp=cwramp[cw_shape];
+ cw_audio_write(0.00003937 * getNextSideToneSample() * cw_keyer_sidetone_volume * ramp);
+ cw_shape_buffer[tx->samples]=ramp;
} else {
//
// If no longer transmitting, or no longer doing CW: reset pulse shaper.
-// This will also swallow any pending CW in rigtl CAT CW and wipe out the
-// cw_shape buffer very quickly. In order to tell rigctl etc. that CW should be
+// This will also swallow any pending CW in rigtl CAT CW and wipe out
+// cw_shape_buffer very quickly. In order to tell rigctl etc. that CW should be
// aborted, we also use the cw_not_ready flag.
//
cw_not_ready=1;
cw_key_up=0;
cw_key_down=0;
cw_shape=0;
- cw_shape_buffer[tx->samples]=0;
+ cw_shape_buffer[tx->samples]=0.0;
}
tx->mic_input_buffer[tx->samples*2]=mic_sample_double;
tx->mic_input_buffer[(tx->samples*2)+1]=0.0; //mic_sample_double;