forked from w7sst/MorseRunner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Contest.pas
917 lines (779 loc) · 26.5 KB
/
Contest.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
//------------------------------------------------------------------------------
//This Source Code Form is subject to the terms of the Mozilla Public
//License, v. 2.0. If a copy of the MPL was not distributed with this
//file, You can obtain one at http://mozilla.org/MPL/2.0/.
//------------------------------------------------------------------------------
unit Contest;
interface
uses
SndTypes, Station, StnColl, MyStn, Ini, Log, System.Classes,
ExchFields,
MovAvg, Mixers, VolumCtl, DxStn;
type
TContest = class
private
LastLoadCallsign : String; // used to minimize call history file reloads
function DxCount: integer;
procedure SwapFilters;
protected
BFarnsworthEnabled : Boolean; // enables Farnsworth timing (e.g. SST Contest)
constructor Create;
function IsReloadRequired(const AUserCallsign : String) : boolean;
procedure SetLastLoadCallsign(const AUserCallsign : String);
function ValidateExchField(const FieldDef: PFieldDefinition;
const Avalue: string) : Boolean;
public
BlockNumber: integer;
Me: TMyStation;
Stations: TStations;
Agc: TVolumeControl;
Filt, Filt2: TMovingAverage;
Modul: TModulator;
RitPhase: Single;
FStopPressed: boolean;
destructor Destroy; override;
procedure Init;
function LoadCallHistory(const AUserCallsign : string) : boolean; virtual; abstract;
function PickStation : integer; virtual; abstract;
procedure DropStation(id : integer); virtual; abstract;
function GetCall(id : integer) : string; virtual; abstract;
procedure GetExchange(id : integer; out station : TDxStation); virtual; abstract;
function GetRandomSerialNR: Integer; virtual;
function GetStationInfo(const ACallsign : string) : string; virtual;
function PickCallOnly : string;
function OnSetMyCall(const AUserCallsign : string; out err : string) : boolean; virtual;
function ValidateMyExchange(const AExchange: string;
ATokens: TStringList;
out AExchError: string): boolean; virtual;
function OnContestPrepareToStart(const AUserCallsign: string;
const ASentExchange : string) : Boolean; virtual;
procedure SerialNrModeChanged; virtual;
function IsFarnsworthAllowed : Boolean;
function GetSentExchTypes(
const AStationKind : TStationKind;
const AMyCallsign : string) : TExchTypes;
function GetRecvExchTypes(
const AStationKind : TStationKind;
const AMyCallsign : string;
const ADxCallsign : string) : TExchTypes;
function GetExchangeTypes(
const AStationKind : TStationKind;
const ARequestedMsgType : TRequestedMsgType;
const AStationCallsign : string) : TExchTypes; virtual;
procedure SendMsg(const AStn: TStation; const AMsg: TStationMessage); virtual;
procedure SendText(const AStn: TStation; const AMsg: string); virtual;
procedure OnWipeBoxes; virtual;
function OnExchangeEdit(const ACall, AExch1, AExch2: string;
out AExchSummary: string; out AExchError: string) : Boolean; virtual;
procedure OnExchangeEditComplete; virtual;
procedure SetHisCall(const ACall: string); virtual;
function CheckEnteredCallLength(const ACall: string;
out AExchError: String) : boolean; virtual;
function ValidateEnteredExchange(const ACall, AExch1, AExch2: string;
out AExchError: String) : boolean; virtual;
procedure SaveEnteredExchToQso(var Qso: TQso; const AExch1, AExch2: string); virtual;
procedure FindQsoErrors(var Qso: TQso; var ACorrections: TStringList);
function ExtractMultiplier(Qso: PQso) : string; virtual;
function Minute: Single;
function GetAudio: TSingleArray;
procedure OnMeFinishedSending;
procedure OnMeStartedSending;
end;
var
Tst: TContest;
implementation
uses
SysUtils, RndFunc, Math, DxOper,
PerlRegEx,
VCL.Graphics, // clDefault
Main, CallLst, DXCC;
{ TContest }
constructor TContest.Create;
begin
Me := TMyStation.CreateStation;
Stations := TStations.Create;
Filt := TMovingAverage.Create(nil);
Modul := TModulator.Create;
Agc := TVolumeControl.Create(nil);
Filt.Points := Round(0.7 * DEFAULTRATE / Ini.BandWidth);
Filt.Passes := 3;
Filt.SamplesInInput := Ini.BufSize;
Filt.GainDb := 10 * Log10(500/Ini.Bandwidth);
Filt2 := TMovingAverage.Create(nil);
Filt2.Passes := Filt.Passes;
Filt2.SamplesInInput := Filt.SamplesInInput;
Filt2.GainDb := Filt.GainDb;
Modul.SamplesPerSec := DEFAULTRATE;
Modul.CarrierFreq := Ini.Pitch;
Agc.NoiseInDb := 76;
Agc.NoiseOutDb := 76;
Agc.AttackSamples := 155; //AGC attack 5 ms
Agc.HoldSamples := 155;
Agc.AgcEnabled := true;
NoActivityCnt :=0;
LastLoadCallsign := '';
BFarnsworthEnabled := false;
Init;
end;
destructor TContest.Destroy;
begin
Me.Free;
FreeAndNil(Stations);
Filt.Free;
Filt2.Free;
Modul.Free;
FreeAndNil(Agc);
inherited;
end;
procedure TContest.Init;
begin
Me.Init;
Stations.Clear;
BlockNumber := 0;
LastLoadCallsign := '';
BFarnsworthEnabled := false;
end;
{
user's home callsign is required when loading some contests
(don't load if user callsign is empty or is the same as last time).
return whether the call history file is valid. This varies by contest.
}
function TContest.IsReloadRequired(const AUserCallsign : string) : boolean;
begin
Result := not (AUserCallsign.IsEmpty or (LastLoadCallsign = AUserCallsign));
end;
// called by LoadCallHistory after loading the call history file.
procedure TContest.SetLastLoadCallsign(const AUserCallsign : String);
begin
LastLoadCallsign := AUserCallsign;
end;
{
Farnsworth timing is supported by certain contests only (initially the
K1USN SST Contest). Derived contests will set BFarnworthEnabled in their
TContest.Create() method.
}
function TContest.IsFarnsworthAllowed : Boolean;
begin
Result := BFarnsworthEnabled;
end;
{
Return a random serial number for the currently selected Serial NR mode
(a menu pick).
}
function TContest.GetRandomSerialNR: Integer;
begin
Result := Ini.SerialNRSettings[Ini.SerialNR].GetNR;
end;
{
GetStationInfo() returns station's DXCC information.
Adding a contest: SbarUpdateStationInfo - update status bar with station info (e.g. FD shows UserText)
Override as needed for each contest.
}
function TContest.GetStationInfo(const ACallsign : string) : string;
begin
Result := gDXCCList.Search(ACallsign);
end;
// helper function to return only a callsign (used by QrnStation)
function TContest.PickCallOnly : string;
var
id : integer;
begin
id := PickStation;
Result := GetCall(id);
end;
{
OnSetMyCall() is called whenever the user's callsign is set.
Can be overriden by derived classes as needed to update contest-specific
settings. Note that derived classes should update contest-specific
settings before calling this function since the Sent Exchange settings
may depend upon this contest-specific information.
Returns whether the call was successful.
}
function TContest.OnSetMyCall(const AUserCallsign : string; out err : string) : boolean;
begin
Me.MyCall:= AUserCallsign;
// update my sent exchange field types
Me.SentExchTypes:= GetSentExchTypes(skMyStation, AUserCallsign);
Result:= True;
end;
{
Parse into two strings [Exch1, Exch2].
Validate each string and set error string in AExchError.
Return True upon success; False otherwise.
}
function TContest.ValidateMyExchange(const AExchange: string;
ATokens: TStringList;
out AExchError: string): boolean;
var
SentExchTypes : TExchTypes;
Field1Def: PFieldDefinition;
Field2Def: PFieldDefinition;
begin
SentExchTypes := Self.Me.SentExchTypes;
Field1Def := @Exchange1Settings[SentExchTypes.Exch1];
Field2Def := @Exchange2Settings[SentExchTypes.Exch2];
// parse into two strings [Exch1, Exch2]
ATokens.Clear;
ExtractStrings([' '], [], PChar(AExchange), ATokens);
if ATokens.Count = 0 then
ATokens.AddStrings(['', '']);
if ATokens.Count = 1 then
ATokens.AddStrings(['']);
// validate sent exchange strings
Result := ValidateExchField(Field1Def, ATokens[0]) and
ValidateExchField(Field2Def, ATokens[1]);
if not Result then
AExchError := Format('Invalid exchange: ''%s'' - expecting %s.',
[AExchange, ActiveContest.Msg]);
end;
function TContest.ValidateExchField(const FieldDef: PFieldDefinition;
const Avalue: string) : Boolean;
var
reg: TPerlRegEx;
s: string;
begin
if SimContest = scNaQp then begin
// special case - I can't figure out how to match an empty string,
// so manually check for an optional string.
s := FieldDef.R;
Result := s.StartsWith('()|(') and Avalue.IsEmpty;
if Result then Exit;
end;
reg := TPerlRegEx.Create();
try
reg.Subject := UTF8Encode(Avalue);
s:= '^(' + FieldDef.R + ')$';
reg.RegEx:= UTF8Encode(s);
Result:= Reg.Match;
finally
reg.Free;
end;
end;
{
OnContestPrepareToStart() event is called whenever a contest is started.
Some contests will override this method to provide additional contest-specfic
behaviors. When overriding this function, be sure to call this base-class
function.
Current behavior is to load the call history file. This action has been
deferred until now since some contests use the user's callsign to determine
which stations can work other stations in the contest. For example, in the
ARRL DX Contest, US/CA Stations work DX (non-US/CA) stations.
Returns whether the operation was successfull.
}
function TContest.OnContestPrepareToStart(const AUserCallsign: string;
const ASentExchange : string) : Boolean;
begin
// reload call history iff user's callsign has changed.
if IsReloadRequired(AUserCallsign) then
begin
// load contest-specific call history file
Result:= LoadCallHistory(AUserCallsign);
// retain user's callsign after successful load
if Result then
SetLastLoadCallsign(AUserCallsign);
end
else
Result:= True;
end;
{
Called after
- 'Setup | Serial NR' menu pick
- 'Setup | Serial NR | Custom Range...' menu pick/modification
The base class implementation does nothing. Other derived classes can
update cached information based on the serial NR menu pick (e.g. CQ WPX).
}
procedure TContest.SerialNrModeChanged;
begin
assert(RunMode <> rmStop);
end;
{
Return sent dynamic exchange types for the given kind-of-station and callsign.
AStationKind represents either the user's station (representing current
simulation) or the DxStn represented a simulated station calling the user.
}
function TContest.GetSentExchTypes(
const AStationKind : TStationKind;
const AMyCallsign : string) : TExchTypes;
begin
Result:= Self.GetExchangeTypes(AStationKind, mtSendMsg, AMyCallsign);
end;
{
Return received dynamic exchange types for the given kind-of-station,
user's (simulation callsign) and the dx station's callsign.
Different contests will use either user's callsign or dx station's callsign.
}
function TContest.GetRecvExchTypes(
const AStationKind : TStationKind;
const AMyCallsign : string;
const ADxCallsign : string) : TExchTypes;
begin
if AStationKind = skMyStation then
Result:= Self.GetExchangeTypes(AStationKind, mtRecvMsg, AMyCallsign)
else
Result:= Self.GetExchangeTypes(AStationKind, mtRecvMsg, ADxCallsign);
end;
function TContest.GetExchangeTypes(
const AStationKind : TStationKind;
const ARequestedMsgType : TRequestedMsgType;
const AStationCallsign : string) : TExchTypes;
begin
Result.Exch1 := ActiveContest.ExchType1;
Result.Exch2 := ActiveContest.ExchType2;
end;
{
This virtual procedure allows contest-specific messages to be implemented
in derived Contest classes.
When overridden by derived classes, if a message is not handled then this
base-class procedure should be called.
Please see ARRLFD.SendMsg for an example.
}
procedure TContest.SendMsg(const AStn: TStation; const AMsg: TStationMessage);
begin
case AMsg of
msgCQ: SendText(AStn, 'CQ <my> TEST');
msgNR: SendText(AStn, '<#>');
msgTU: SendText(AStn, 'TU');
msgMyCall: SendText(AStn, '<my>');
msgHisCall: SendText(AStn, '<his>');
msgB4: SendText(AStn, 'QSO B4');
msgQm: SendText(AStn, '?');
msgNil: if Ini.F8.IsEmpty then SendText(AStn, 'NIL')
else SendText(Astn, Ini.F8);
msgR_NR: SendText(AStn, 'R <#>');
msgR_NR2: SendText(AStn, 'R <#> <#>');
msgDeMyCall1: SendText(AStn, 'DE <my>');
msgDeMyCall2: SendText(AStn, 'DE <my> <my>');
msgDeMyCallNr1: SendText(AStn, 'DE <my> <#>');
msgDeMyCallNr2: SendText(AStn, 'DE <my> <my> <#>');
msgMyCall2: SendText(AStn, '<my> <my>');
msgMyCallNr1: SendText(AStn, '<my> <#>');
msgMyCallNr2: SendText(AStn, '<my> <my> <#>');
msgNrQm: SendText(AStn, 'NR?');
msgLongCQ: SendText(AStn, 'CQ CQ TEST <my> <my> TEST'); // QrmStation only
msgQrl: SendText(AStn, 'QRL?');
msgQrl2: SendText(AStn, 'QRL? QRL?');
msqQsy: SendText(AStn, '<his> QSY QSY');
msgAgn: SendText(AStn, 'AGN');
end;
end;
{
This virtual procedure is provided to allow a derived contest the ability
to perform additional processing on the message, including token replacement,
before being passed to the Encoder and Keyer.
}
procedure TContest.SendText(const AStn: TStation; const AMsg: string);
begin
AStn.SendText(AMsg); // virtual
end;
{
Called at end of each QSO or by user's Cntl-W (Wipe Boxes) keystroke.
}
procedure TContest.OnWipeBoxes;
begin
Log.NrSent := False;
Log.DisplayError('', clDefault);
end;
{
Called after each keystroke of the Exch2 field (Edit3).
}
function TContest.OnExchangeEdit(const ACall, AExch1, AExch2: string;
out AExchSummary: string; out AExchError: string) : Boolean;
begin
AExchSummary := '';
Result := False;
end;
{
Called at the start of each action/command after user has finished typing
in the Exchange fields. Can be overriden as needed for complex exchange
behaviors (e.g. ARRL SS).
}
procedure TContest.OnExchangeEditComplete;
begin
Log.CallSent := (Mainform.Edit1.Text <> '') and
(Mainform.Edit1.Text = Self.Me.HisCall);
end;
{
SetHisCall will:
- sets TContest.Me.HisCall to the supplied callsign, ACall.
- sets Log.CallSent to False if the callsign should be sent.
Override as needed to provide more complex callsign behaviors (e.g. ARRL
Sweepstakes allows callsign corrections in the exchange).
}
procedure TContest.SetHisCall(const ACall: string);
begin
if ACall <> '' then Self.Me.HisCall := ACall;
Log.CallSent := ACall <> '';
end;
{
Find exchange errors in the current Qso.
Called at end of each Qso during Qso validaiton.
This virtual procedure can be overriden to perform special exchange
validation behaviors.
Side Effects:
- sets Qso.Exch1Error and Qso.Exch2Error
- add exchange corrections to ACorrection
}
procedure TContest.FindQsoErrors(var Qso: TQso; var ACorrections: TStringList);
begin
Qso.CheckExch1(ACorrections);
Qso.CheckExch2(ACorrections);
end;
{
Performs simple length check on a callsign.
Returns true for callsigns with 3 or more characters; false otherwise.
Upon error, AExchError will contain a simple error message.
}
function TContest.CheckEnteredCallLength(const ACall: string;
out AExchError: String) : boolean;
begin
Result := StringReplace(ACall, '?', '', [rfReplaceAll]).Length >= 3;
if not Result then
AExchError := 'Invalid callsign';
end;
{
ValidateEnteredExchange is called prior to sending the final 'TU' and calling
SaveQSO (see Log.pas). The basic validation is a length test where each
exchange is checked against a minimum length requirement.
This is consistent with original 1.68 behaviors.
This virtual function can be overriden for complex exchange information
(e.g. ARRL Sweepstakes).
}
function TContest.ValidateEnteredExchange(const ACall, AExch1, AExch2: string;
out AExchError: String) : boolean;
// Adding a contest: validate contest-specific exchange fields
//validate Exchange 1 (Edit2) field lengths
function ValidateExchField1(const text: string): Boolean;
begin
Result := false;
case Mainform.RecvExchTypes.Exch1 of
etRST: Result := Length(text) = 3;
etOpName: Result := Length(text) > 1;
etFdClass: Result := Length(text) > 1;
else
assert(false, 'missing case');
end;
end;
//validate Exchange 2 (Edit3) field lengths
function ValidateExchField2(const text: string): Boolean;
begin
Result := false;
case Mainform.RecvExchTypes.Exch2 of
etSerialNr: Result := Length(text) > 0;
etGenericField:Result := Length(text) > 0;
etArrlSection: Result := Length(text) > 1;
etStateProv: Result := Length(text) > 1;
etCqZone: Result := Length(text) > 0;
etItuZone: Result := Length(text) > 0;
//etAge:
etPower: Result := Length(text) > 0;
etJaPref: Result := Length(text) > 2;
etJaCity: Result := Length(text) > 3;
etNaQpExch2: Result := Length(text) > 0;
etNaQpNonNaExch2: Result := Length(text) >= 0;
else
assert(false, 'missing case');
end;
end;
begin
if not ValidateExchField1(AExch1) then
AExchError := format('Missing/Invalid %s',
[Exchange1Settings[Mainform.RecvExchTypes.Exch1].C])
else if not ValidateExchField2(AExch2) then
AExchError := format('Missing/Invalid %s',
[Exchange2Settings[Mainform.RecvExchTypes.Exch2].C])
else
AExchError := '';
Result := AExchError.IsEmpty;
end;
{
SaveEnteredExchToQso will save contest-specific exchange values into a QSO.
This is called by SaveQSO while saving the completed QSO into the log.
This virtual function can be overriden by specialized contests as needed
(see ARRL Sweepstakes).
}
procedure TContest.SaveEnteredExchToQso(var Qso: TQso; const AExch1, AExch2: string);
begin
// Adding a contest: save contest-specific exchange values into QsoList
//save Exchange 1 (Edit2)
case Mainform.RecvExchTypes.Exch1 of
etRST: Qso.Rst := StrToIntDef(AExch1, 0);
etOpName: Qso.Exch1 := AExch1;
etFdClass: Qso.Exch1 := AExch1;
else
assert(false, 'missing case');
end;
//save Exchange2 (Edit3)
case Mainform.RecvExchTypes.Exch2 of
etSerialNr: Qso.Nr := StrToIntDef(AExch2, 0);
etGenericField:Qso.Exch2 := AExch2;
etArrlSection: Qso.Exch2 := AExch2;
etStateProv: Qso.Exch2 := AExch2;
etCqZone: Qso.Exch2 := AExch2;
etItuZone: Qso.Exch2 := AExch2;
//etAge:
etPower: Qso.Exch2 := AExch2;
etJaPref: Qso.Exch2 := AExch2;
etJaCity: Qso.Exch2 := AExch2;
etNaQpExch2: Qso.Exch2 := AExch2;
etNaQpNonNaExch2:
if AExch2 = '' then
Qso.Exch2 := 'DX'
else
Qso.Exch2 := AExch2;
else
assert(false, 'missing case');
end;
if Qso.Exch1.IsEmpty then Qso.Exch1 := '?';
if Qso.Exch2.IsEmpty then Qso.Exch2 := '?';
end;
{
Extract multiplier string for a given contest. Default behavior will
return the QSO.Pfx string (which implies this method must be called
after ExtractPrefix.
Also sets contest-specific Qso.Points for this QSO.
Derived contests will override this method when contest rules require
different multiplier rules or QSO points.
For example, ARRL DX Rules state: "Multiply total QSO points by the number
of DXCC entities (W/VE stations) or states and provinces (DX stations)
contacted to get your final score."
Return the multiplier string used by this contest. This string is accumlated
in the Log.RawMultList and Log.VerifiedMultList to count the multiplier value.
}
function TContest.ExtractMultiplier(Qso: PQso) : string;
begin
Qso.Points := 1;
// assumes Log.ExtractPrefix() has already been called.
Result := Qso.Pfx;
end;
function TContest.GetAudio: TSingleArray;
const
NOISEAMP = 6000;
var
ReIm: TReImArrays;
Blk: TSingleArray;
i, Stn: integer;
Bfo: Single;
Smg, Rfg: Single;
begin
//minimize audio output delay
SetLength(Result, 1);
Inc(BlockNumber);
if BlockNumber < 6 then Exit;
//complex noise
SetLengthReIm(ReIm, Ini.BufSize);
for i:=0 to High(ReIm.Re) do
begin
ReIm.Re[i] := 3 * NOISEAMP * (Random-0.5);
ReIm.Im[i] := 3 * NOISEAMP * (Random-0.5);
end;
//QRN
if Ini.Qrn then
begin
//background
for i:=0 to High(ReIm.Re) do
if Random < 0.01 then ReIm.Re[i] := 60 * NOISEAMP * (Random-0.5);
//burst
if Random < 0.01 then Stations.AddQrn;
end;
//QRM
if Ini.Qrm and (Random < 0.0002) then Stations.AddQrm;
//audio from stations
Blk := nil;
for Stn:=0 to Stations.Count-1 do
if Stations[Stn].State = stSending then
begin
Blk := Stations[Stn].GetBlock;
for i:=0 to High(Blk) do
begin
Bfo := Stations[Stn].Bfo - RitPhase - i * TWO_PI * Ini.Rit / DEFAULTRATE;
ReIm.Re[i] := ReIm.Re[i] + Blk[i] * Cos(Bfo);
ReIm.Im[i] := ReIm.Im[i] - Blk[i] * Sin(Bfo);
end;
end;
//Rit
RitPhase := RitPhase + Ini.BufSize * TWO_PI * Ini.Rit / DEFAULTRATE;
while RitPhase > TWO_PI do RitPhase := RitPhase - TWO_PI;
while RitPhase < -TWO_PI do RitPhase := RitPhase + TWO_PI;
//my audio
if Me.State = stSending then
begin
Blk := Me.GetBlock;
//self-mon. gain
Smg := Power(10, (MainForm.VolumeSlider1.Value - 0.75) * 4);
Rfg := 1;
for i:=0 to High(Blk) do
if Ini.Qsk
then
begin
if Rfg > (1 - Blk[i]/Me.Amplitude)
then Rfg := (1 - Blk[i]/Me.Amplitude)
else Rfg := Rfg * 0.997 + 0.003;
ReIm.Re[i] := Smg * Blk[i] + Rfg * ReIm.Re[i];
ReIm.Im[i] := Smg * Blk[i] + Rfg * ReIm.Im[i];
end
else
begin
ReIm.Re[i] := Smg * (Blk[i]);
ReIm.Im[i] := Smg * (Blk[i]);
end;
end;
//LPF
Filt2.Filter(ReIm);
ReIm := Filt.Filter(ReIm);
if (BlockNumber mod 10) = 0 then SwapFilters;
//mix up to Pitch frequency
Result := Modul.Modulate(ReIm);
//AGC
Result := Agc.Process(Result);
//save
with MainForm.AlWavFile1 do
if IsOpen then WriteFrom(@Result[0], nil, Ini.BufSize);
//timer tick
Me.Tick;
for Stn:=Stations.Count-1 downto 0 do Stations[Stn].Tick;
//if DX is done, write to log and kill
for i:=Stations.Count-1 downto 0 do
if Stations[i] is TDxStation then
with Stations[i] as TDxStation do
if (Oper.State = osDone) and (QsoList <> nil) and
((MyCall = QsoList[High(QsoList)].Call) or
(Oper.IsMyCall(QsoList[High(QsoList)].Call, False) = mcAlmost)) then begin
// grab Qso's "True" data (e.g. TrueCall, TrueExch1, TrueExch2)
DataToLastQso; // deletes this TDxStation from Stations[]
// rerun error check and update Err string on screen log
Log.CheckErr;
Log.ScoreTableUpdateCheck;
{ TODO -omikeb -cfeature : Clean up status bar code. }
if SimContest = scHst then
Log.UpdateStatsHst
else
Log.UpdateStats({AVerifyResults=}True);
{
This code can be used to clear QSO info after 'TU' is sent.
However, this may be a multi-threading issue here because
this audio thread will be changing things being manipulated
by the GUI thread. Need more time to think through this one.
// clear any errors/status from last QSO
Log.DisplayError('', clDefault);
Log.SBarUpdateSummary('');
}
end;
//show info
ShowRate;
MainForm.Panel2.Caption := FormatDateTime('hh:nn:ss', BlocksToSeconds(BlockNumber) / 86400);
if Ini.RunMode = rmPileUp then
MainForm.Panel4.Caption := Format('Pile-Up: %d', [DxCount]);
if (RunMode = rmSingle) and (DxCount = 0) then begin
Me.Msg := [msgCq]; //no need to send cq in this mode
Stations.AddCaller.ProcessEvent(evMeFinished);
{$ifdef DEBUG}
if Main.BDebugExchSettings then
begin
MainForm.Edit1.Text := DxStn.LastDxCallsign;
MainForm.Edit2.Text := '';
MainForm.Edit3.Text := '';
Log.CallSent := False; // my Call hasn't been sent to this new station
Log.NrSent := False; // my Exch hasn't been sent to this new station
end;
{$endif}
end
else
if (RunMode = rmHst) and (DxCount < Activity) then begin
Me.Msg := [msgCq];
for i:=DxCount+1 to Activity do
Stations.AddCaller.ProcessEvent(evMeFinished);
end;
if (BlocksToSeconds(BlockNumber) >= (Duration * 60)) or FStopPressed then
begin
if RunMode = rmHst then
begin
MainForm.Run(rmStop);
FStopPressed := false;
MainForm.PopupScoreHst;
end
else if (SimContest = scWpx) and
(RunMode in [rmHst, rmWpx]) and
not FStopPressed then
begin
MainForm.Run(rmStop);
FStopPressed := false;
MainForm.PopupScoreWpx;
end
else
begin
MainForm.Run(rmStop);
FStopPressed := false;
end;
{
if (RunMode in [rmWpx, rmHst]) and not FStopPressed
then begin MainForm.Run(rmStop); MainForm.PopupScore; end
else MainForm.Run(rmStop);
}
end;
end;
function TContest.DxCount: integer;
var
i: integer;
begin
Result := 0;
for i:=Stations.Count-1 downto 0 do
if (Stations[i] is TDxStation) and
(TDxStation(Stations[i]).Oper.State <> osDone)
then Inc(Result);
end;
function TContest.Minute: Single;
begin
Result := BlocksToSeconds(BlockNumber) / 60;
end;
procedure TContest.OnMeFinishedSending;
var
i: integer;
z: integer;
Dx : integer;
begin
//the stations heard my CQ and want to call
if (not (RunMode in [rmSingle, RmHst])) then
if (msgCQ in Me.Msg) or
((QsoList <> nil) and ((msgTU in Me.Msg) or (msgMyCall in Me.Msg))) then
begin
z := 0;
Dx := DxCount;
if not (msgCQ in Me.Msg) then
if Dx > 0 then Dec(Dx); // The just finished Q has to be deducted
for i:=1 to RndPoisson(Activity / 2) - Dx do
begin
Stations.AddCaller;
z := 1;
end;
if z=0 then begin
// No maximo fica 3 cq sem contesters
// (At most 3 cq without contesters)
inc(NoActivityCnt);
if ((NoActivityCnt > 2) or (NoStopActivity > 0) ) then begin
Stations.AddCaller;
NoActivityCnt := 0;
end;
end;
end;
//tell callers that I finished sending
for i:=Stations.Count-1 downto 0 do
Stations[i].ProcessEvent(evMeFinished);
end;
procedure TContest.OnMeStartedSending;
var
i: integer;
begin
//tell callers that I started sending
for i:=Stations.Count-1 downto 0 do
Stations[i].ProcessEvent(evMeStarted);
end;
procedure TContest.SwapFilters;
var
F: TMovingAverage;
begin
F := Filt;
Filt := Filt2;
Filt2 := F;
Filt2.Reset;
end;
end.