Skip to content

Commit 0f6d129

Browse files
Dennis Sheirerdenny
Dennis Sheirer
authored and
denny
committed
#2212 Introduce carrier offset measurement processor to monitor a 12.5 kHz channel and provide estimated carrier offset measurements that allow the tuner's frequency error monitor to correct for the average error reported from each channel. Uses a 15 dB threshold where the signal must be at least 15 dB above the noise floor before it will calculate and broadcast the estimate. Stores the updated PPM in the tuner configuration after each update to ensure the value persists across sessions. When selected in the tuner panel, updates the displayed PPM value after each PPM autocorrection.
1 parent afadb22 commit 0f6d129

27 files changed

+883
-280
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,153 @@
1-
/*******************************************************************************
2-
* SDR Trunk
3-
* Copyright (C) 2014 Dennis Sheirer
4-
*
5-
* This program is free software: you can redistribute it and/or modify
6-
* it under the terms of the GNU General Public License as published by
7-
* the Free Software Foundation, either version 3 of the License, or
8-
* (at your option) any later version.
9-
*
10-
* This program is distributed in the hope that it will be useful,
11-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13-
* GNU General Public License for more details.
14-
*
15-
* You should have received a copy of the GNU General Public License
16-
* along with this program. If not, see <http://www.gnu.org/licenses/>
17-
******************************************************************************/
1+
/*
2+
* *****************************************************************************
3+
* Copyright (C) 2014-2025 Dennis Sheirer
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>
17+
* ****************************************************************************
18+
*/
1819
package io.github.dsheirer.buffer;
1920

21+
import java.util.Arrays;
22+
23+
/**
24+
* Averaging buffer backed by a ring buffer implemented with a float array.
25+
*/
2026
public class FloatAveragingBuffer
2127
{
22-
private float[] mBuffer;
23-
private float mAverage = 0.0f;
24-
private int mBufferSize;
25-
private int mBufferPointer;
26-
27-
public FloatAveragingBuffer( int size )
28-
{
29-
mBufferSize = size;
30-
mBuffer = new float[ size ];
31-
}
32-
33-
public float get( float newValue )
34-
{
35-
float oldValue = mBuffer[ mBufferPointer ];
36-
37-
if( Float.isInfinite( newValue ) || Float.isNaN( newValue ) )
38-
{
39-
mAverage = mAverage - ( oldValue / mBufferSize );
40-
41-
mBuffer[ mBufferPointer++ ] = 0.0f;
42-
}
43-
else
44-
{
45-
mAverage = mAverage - ( oldValue / mBufferSize ) + ( newValue / mBufferSize );
46-
47-
mBuffer[ mBufferPointer++ ] = newValue;
48-
}
49-
50-
if( mBufferPointer >= mBufferSize )
51-
{
52-
mBufferPointer = 0;
53-
}
54-
55-
return mAverage;
56-
}
28+
private final float[] mBuffer;
29+
private final int mBufferSize;
30+
private float mAverage = 0.0f;
31+
private int mBufferPointer;
32+
private boolean mRapidFill = false;
33+
private int mRapidFillCounter = 0;
34+
private int mRapidFillIncrement;
35+
36+
/**
37+
* Constructs an instance that uses a rapid fill increment value where the initial values get counted multiple times
38+
* to more quickly get to the average value instead of walking up to the average from the default average value of 0.
39+
* @param size of buffer
40+
* @param rapidFillIncrement indicating how many value additions per individual add to execute until the averaging
41+
* buffer is filled. After the initial fill state is achieved, reverts to using a single value addition.
42+
*/
43+
public FloatAveragingBuffer(int size, int rapidFillIncrement)
44+
{
45+
this(size);
46+
mRapidFillIncrement = rapidFillIncrement;
47+
mRapidFill = true;
48+
}
49+
50+
/**
51+
* Constructs an instance that uses a ring buffer of the specified size.
52+
* @param size of the averaging buffer.
53+
*/
54+
public FloatAveragingBuffer(int size)
55+
{
56+
mBufferSize = size;
57+
mBuffer = new float[size];
58+
}
59+
60+
/**
61+
* Resets this buffer to an empty state
62+
*/
63+
public void reset()
64+
{
65+
Arrays.fill(mBuffer, 0);
66+
mBufferPointer = 0;
67+
mAverage = 0.0f;
68+
69+
if(mRapidFillIncrement > 0)
70+
{
71+
mRapidFill = true;
72+
mRapidFillCounter = 0;
73+
}
74+
}
75+
76+
/**
77+
* Current average value.
78+
* @return average
79+
*/
80+
public float getAverage()
81+
{
82+
return mAverage;
83+
}
84+
85+
/**
86+
* Adds the value to the buffer and updates the average.
87+
* @param value to add
88+
*/
89+
public void add(float value)
90+
{
91+
if(mRapidFill)
92+
{
93+
for(int x = 0; x < mRapidFillIncrement; x++)
94+
{
95+
float oldValue = mBuffer[mBufferPointer];
96+
97+
if(Float.isInfinite(value) || Float.isNaN(value))
98+
{
99+
mAverage = mAverage - (oldValue / mBufferSize);
100+
mBuffer[mBufferPointer++] = 0.0f;
101+
}
102+
else
103+
{
104+
mAverage = mAverage - (oldValue / mBufferSize) + (value / mBufferSize);
105+
mBuffer[mBufferPointer++] = value;
106+
}
107+
108+
if(mBufferPointer >= mBufferSize)
109+
{
110+
mBufferPointer = 0;
111+
}
112+
}
113+
114+
mRapidFillCounter += mRapidFillIncrement;
115+
116+
if(mRapidFillCounter > mBufferSize)
117+
{
118+
mRapidFill = false;
119+
}
120+
}
121+
else
122+
{
123+
float oldValue = mBuffer[mBufferPointer];
124+
125+
if(Float.isInfinite(value) || Float.isNaN(value))
126+
{
127+
mAverage = mAverage - (oldValue / mBufferSize);
128+
mBuffer[mBufferPointer++] = 0.0f;
129+
}
130+
else
131+
{
132+
mAverage = mAverage - (oldValue / mBufferSize) + (value / mBufferSize);
133+
mBuffer[mBufferPointer++] = value;
134+
}
135+
136+
if(mBufferPointer >= mBufferSize)
137+
{
138+
mBufferPointer = 0;
139+
}
140+
}
141+
}
142+
143+
/**
144+
* Adds the value to the buffer and calculates a new average.
145+
* @param value to add
146+
* @return updated average
147+
*/
148+
public float get(float value)
149+
{
150+
add(value);
151+
return mAverage;
152+
}
57153
}

src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2024 Dennis Sheirer
3+
* Copyright (C) 2014-2025 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -302,10 +302,17 @@ public void process(SourceEvent sourceEvent) throws SourceException
302302
break;
303303
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
304304
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
305-
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
306305
case NOTIFICATION_RECORDING_FILE_LOADED:
307306
//no-op
308307
break;
308+
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
309+
//Re-broadcast this event to each channel so that the decoders can reset error tracking function(s).
310+
List<TunerChannelSource> channels = new ArrayList<>(mChannelSources);
311+
for(TunerChannelSource channel: channels)
312+
{
313+
channel.broadcastConsumerSourceEvent(sourceEvent);
314+
}
315+
break;
309316
default:
310317
mLog.info("Unrecognized source event: " + sourceEvent);
311318
break;

src/main/java/io/github/dsheirer/dsp/psk/pll/FrequencyCorrectionSyncMonitor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2023 Dennis Sheirer
3+
* Copyright (C) 2014-2025 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -98,7 +98,7 @@ public void reset()
9898
@Override
9999
public void processFrequencyError(long frequencyError)
100100
{
101-
mFeedbackDecoder.broadcast(SourceEvent.pllFrequencyMeasurement(frequencyError));
101+
mFeedbackDecoder.broadcast(SourceEvent.carrierOffsetMeasurement(-frequencyError));
102102

103103
//Only rebroadcast as a frequency error measurement if the sync count is more than 2
104104
if(mSyncCount > 2)

src/main/java/io/github/dsheirer/dsp/squelch/PowerMonitor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2022 Dennis Sheirer
3+
* Copyright (C) 2014-2025 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -98,7 +98,7 @@ public void setSourceEventListener(Listener<SourceEvent> listener)
9898
/**
9999
* Broadcasts the source event to an optional register listener
100100
*/
101-
private void broadcast(SourceEvent event)
101+
public void broadcast(SourceEvent event)
102102
{
103103
if(mSourceEventListener != null)
104104
{

0 commit comments

Comments
 (0)