mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Closes #7 NXDN Decoder * Implements: NXDN4800, NXDN9600, Type-C and Type-D decoding and trunk tracking. * Redesigns tuner and channel frequency error management.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.source;
|
||||
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
|
||||
/**
|
||||
* Notification of the current tuner, channel and decoder frequency correction state.
|
||||
*/
|
||||
public class ChannelFrequencyCorrectionStatusNotification extends SourceEvent
|
||||
{
|
||||
private long mChannelCorrection;
|
||||
private long mTunerCorrection;
|
||||
private double mTunerPPM;
|
||||
private boolean mAutoPPM;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param source tuner channel
|
||||
* @param decoderCorrection from the channel decoder
|
||||
* @param channelCorrection currently applied frequency correction
|
||||
* @param tunerPPM current tuner PPM value
|
||||
* @param tunerCorrection current tuner frequency correction value.
|
||||
* @param autoPPM indicating if tuner autoPPM is enabled.
|
||||
*/
|
||||
private ChannelFrequencyCorrectionStatusNotification(TunerChannelSource source, long decoderCorrection,
|
||||
long channelCorrection, double tunerPPM, long tunerCorrection,
|
||||
boolean autoPPM)
|
||||
{
|
||||
super(SourceEvent.Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_STATUS, source, decoderCorrection, "Status Report");
|
||||
mChannelCorrection = channelCorrection;
|
||||
mTunerCorrection = tunerCorrection;
|
||||
mTunerPPM = tunerPPM;
|
||||
mAutoPPM = autoPPM;
|
||||
}
|
||||
|
||||
public boolean isAutoPPM()
|
||||
{
|
||||
return mAutoPPM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current tuner frequency correction value
|
||||
*/
|
||||
public long getTunerCorrection()
|
||||
{
|
||||
return mTunerCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current tuner PPM setting
|
||||
*/
|
||||
public double getTunerPPM()
|
||||
{
|
||||
return mTunerPPM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel correction value
|
||||
*/
|
||||
public long getChannelCorrection()
|
||||
{
|
||||
return mChannelCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correction applied by the decoder which is the requested change (from the decoder)
|
||||
*/
|
||||
public long getDecoderCorrection()
|
||||
{
|
||||
return getValue().longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to create an instance
|
||||
* @param source tuner channel
|
||||
* @param decoderCorrection from the decoder
|
||||
* @param channelCorrection currently applied to the channel tuner source
|
||||
* @param tunerPPM reported by the tuner
|
||||
* @param tunerCorrection in Hertz reported by the tuner
|
||||
* @param autoPPM indicating the enabled state of the tuner frequency correction manager
|
||||
* @return new instance
|
||||
*/
|
||||
public static ChannelFrequencyCorrectionStatusNotification create(TunerChannelSource source, long decoderCorrection,
|
||||
long channelCorrection, double tunerPPM,
|
||||
long tunerCorrection, boolean autoPPM)
|
||||
{
|
||||
return new ChannelFrequencyCorrectionStatusNotification(source, decoderCorrection, channelCorrection,
|
||||
tunerPPM, tunerCorrection, autoPPM);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -31,6 +31,7 @@ public class SourceEvent
|
||||
NOTIFICATION_CHANNEL_COUNT_CHANGE,
|
||||
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION,
|
||||
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
||||
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_STATUS,
|
||||
NOTIFICATION_CHANNEL_POWER,
|
||||
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
|
||||
NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED,
|
||||
@@ -40,7 +41,6 @@ public class SourceEvent
|
||||
NOTIFICATION_FREQUENCY_ROTATION_SUCCESS,
|
||||
NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
|
||||
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
|
||||
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
|
||||
NOTIFICATION_CARRIER_OFFSET_FREQUENCY,
|
||||
NOTIFICATION_RECORDING_FILE_LOADED,
|
||||
NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||
@@ -51,6 +51,7 @@ public class SourceEvent
|
||||
NOTIFICATION_ERROR_STATE,
|
||||
|
||||
REQUEST_FREQUENCY_CHANGE,
|
||||
REQUEST_FREQUENCY_CORRECTION,
|
||||
REQUEST_FREQUENCY_ROTATION,
|
||||
REQUEST_FREQUENCY_SELECTION,
|
||||
REQUEST_CHANGE_SQUELCH_THRESHOLD,
|
||||
@@ -66,15 +67,15 @@ public class SourceEvent
|
||||
EnumSet.range(NOTIFICATION_CHANNEL_COUNT_CHANGE, NOTIFICATION_SAMPLE_RATE_CHANGE);
|
||||
}
|
||||
|
||||
private Event mEvent;
|
||||
private Number mValue;
|
||||
private final Event mEvent;
|
||||
private final Number mValue;
|
||||
private Source mSource;
|
||||
private String mEventDescription;
|
||||
private final String mEventDescription;
|
||||
|
||||
/**
|
||||
* Private constructor. Use the static constructor methods to create an event.
|
||||
*/
|
||||
private SourceEvent(Event event, Source source, Number value, String eventDescription)
|
||||
public SourceEvent(Event event, Source source, Number value, String eventDescription)
|
||||
{
|
||||
mEvent = event;
|
||||
mSource = source;
|
||||
@@ -244,6 +245,16 @@ public class SourceEvent
|
||||
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new frequency correction request emitted by the FeedbackDecoder
|
||||
*
|
||||
* @param correction in hertz
|
||||
*/
|
||||
public static SourceEvent frequencyCorrectionRequest(long correction)
|
||||
{
|
||||
return new SourceEvent(Event.REQUEST_FREQUENCY_CORRECTION, correction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new carrier offset measurement notification event.
|
||||
*
|
||||
@@ -264,18 +275,6 @@ public class SourceEvent
|
||||
return new SourceEvent(Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR, frequencyError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new frequency error measurement notification event. This event is different from
|
||||
* the raw frequency error measurement and indicates that the current state of the PLL is
|
||||
* locked and tracking the signal.
|
||||
*
|
||||
* @param frequencyError in hertz
|
||||
*/
|
||||
public static SourceEvent frequencyErrorMeasurementSyncLocked(long frequencyError, String eventDescription)
|
||||
{
|
||||
return new SourceEvent(Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED, frequencyError, eventDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sample rate change event
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -40,21 +40,18 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Tuner.class);
|
||||
|
||||
private Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
|
||||
private final Broadcaster<TunerEvent> mTunerEventBroadcaster = new Broadcaster<>();
|
||||
private ChannelSourceManager mChannelSourceManager;
|
||||
private TunerController mTunerController;
|
||||
private TunerFrequencyErrorMonitor mTunerFrequencyErrorMonitor;
|
||||
private final TunerController mTunerController;
|
||||
private ITunerErrorListener mTunerErrorListener;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private final AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
public Tuner(TunerController tunerController, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
mTunerErrorListener = tunerErrorListener;
|
||||
//Register to receive frequency and sample rate change notifications
|
||||
mTunerController.addListener(this::process);
|
||||
mTunerFrequencyErrorMonitor = new TunerFrequencyErrorMonitor(this);
|
||||
mTunerFrequencyErrorMonitor.start();
|
||||
mTunerController.addListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +126,6 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
getTunerController().dispose();
|
||||
|
||||
mTunerEventBroadcaster.clear();
|
||||
mTunerFrequencyErrorMonitor = null;
|
||||
mTunerErrorListener = null;
|
||||
}
|
||||
}
|
||||
@@ -195,6 +191,9 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_FREQUENCY_ERROR));
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_MEASURED_FREQUENCY_ERROR));
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_SAMPLE_RATE));
|
||||
break;
|
||||
@@ -202,9 +201,6 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
||||
broadcast(new TunerEvent(Tuner.this, Event.UPDATE_LOCK_STATE));
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
mTunerFrequencyErrorMonitor.receive(event);
|
||||
break;
|
||||
case NOTIFICATION_RECORDING_FILE_LOADED:
|
||||
//ignore
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -35,7 +35,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
|
||||
import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -61,8 +61,8 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
private SourceEventListenerToProcessorAdapter mSourceEventListener;
|
||||
private NativeBufferWaveRecorder mRecorder;
|
||||
private ITunerErrorListener mTunerErrorListener;
|
||||
private DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0");
|
||||
private FrequencyErrorCorrectionManager mFrequencyErrorCorrectionManager;
|
||||
private static final DecimalFormat DF = new DecimalFormat("0.000");
|
||||
protected TunerFrequencyErrorManager mTunerFrequencyErrorManager;
|
||||
|
||||
/**
|
||||
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
|
||||
@@ -74,7 +74,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
mTunerErrorListener = tunerErrorListener;
|
||||
mFrequencyController = new FrequencyController(this);
|
||||
mSourceEventListener = new SourceEventListenerToProcessorAdapter(this);
|
||||
mFrequencyErrorCorrectionManager = new FrequencyErrorCorrectionManager(this);
|
||||
mTunerFrequencyErrorManager = new TunerFrequencyErrorManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +82,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
* configuration changes happen atomically. There are at least two threads that can touch this controller:
|
||||
*
|
||||
* A: channel processing that changes the center tuned frequency and frequency correction.
|
||||
* b: user ui thread that can touch any of the frequency correction and gain controls.
|
||||
* B: user ui thread that can touch any of the frequency correction and gain controls.
|
||||
*
|
||||
* Generally, we'll lock on each of the primary configuration change points - getter and setter for:
|
||||
* Frequency
|
||||
@@ -97,6 +97,20 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
return mLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-PPM Tuner frequency error manager. Works in tandem with the ChannelFrequencyErrorManager instance in each
|
||||
* of the child tuner channel sources to receive feedback from the primary channel decoder of frequency correction
|
||||
* needed to align the signal within the channel. When enabled, this manager automatically updates the tuner's PPM
|
||||
* value from the average residual error across each of the allocated channels.
|
||||
*
|
||||
* Note: each channel will independently correct any residual error after the tuner makes adjustements.
|
||||
* @return tuner frequency error manager
|
||||
*/
|
||||
public TunerFrequencyErrorManager getTunerFrequencyErrorManager()
|
||||
{
|
||||
return mTunerFrequencyErrorManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the frequency controller with the new minimum and maximum values.
|
||||
* @param minimum frequency Hertz
|
||||
@@ -108,17 +122,9 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
mFrequencyController.setMaximumFrequency(maximum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency correction manager for this tuner controller.
|
||||
*/
|
||||
public FrequencyErrorCorrectionManager getFrequencyErrorCorrectionManager()
|
||||
{
|
||||
return mFrequencyErrorCorrectionManager;
|
||||
}
|
||||
|
||||
protected void dispose()
|
||||
{
|
||||
getFrequencyErrorCorrectionManager().dispose();
|
||||
mTunerFrequencyErrorManager.dispose();
|
||||
mNativeBufferBroadcaster.clear();
|
||||
mFrequencyController.dispose();
|
||||
mSourceEventListener.dispose();
|
||||
@@ -224,7 +230,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
setMaximumFrequency(config.getMaximumFrequency());
|
||||
}
|
||||
setFrequencyCorrection(config.getFrequencyCorrection());
|
||||
getFrequencyErrorCorrectionManager().setEnabled(config.getAutoPPMCorrectionEnabled());
|
||||
getTunerFrequencyErrorManager().setEnabled(config.getAutoPPMCorrectionEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,19 +355,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
|
||||
public double getFrequencyCorrection()
|
||||
{
|
||||
double correction;
|
||||
|
||||
try
|
||||
{
|
||||
getLock().lock();
|
||||
correction = mFrequencyController.getFrequencyCorrection();
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
|
||||
return correction;
|
||||
return mFrequencyController.getFrequencyCorrection();
|
||||
}
|
||||
|
||||
public void setFrequencyCorrection(double correction) throws SourceException
|
||||
@@ -556,10 +550,10 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
if(hasMeasuredFrequencyError())
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Measured Error: ");
|
||||
sb.append("Average Channel Error: ");
|
||||
sb.append(getMeasuredFrequencyError());
|
||||
sb.append("Hz (");
|
||||
sb.append(mFrequencyErrorPPMFormat.format(getPPMFrequencyError()));
|
||||
sb.append(" Hz (");
|
||||
sb.append(DF.format(getPPMFrequencyError()));
|
||||
sb.append("ppm)");
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -590,13 +584,21 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the measured frequency error average.
|
||||
* @param measuredFrequencyError in hertz averaged over a 5 second interval.
|
||||
* Sets the measured frequency error average from the TunerFrequencyErrorManager
|
||||
* @param measuredFrequencyError in hertz
|
||||
*/
|
||||
public void setMeasuredFrequencyError(int measuredFrequencyError)
|
||||
{
|
||||
mMeasuredFrequencyError = measuredFrequencyError;
|
||||
getFrequencyErrorCorrectionManager().updatePPM(getPPMFrequencyError());
|
||||
|
||||
try
|
||||
{
|
||||
mFrequencyController.broadcast(SourceEvent.frequencyErrorMeasurement(measuredFrequencyError));
|
||||
}
|
||||
catch(SourceException e)
|
||||
{
|
||||
//Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Monitors frequency error measurements received from certain decoders (e.g. P25) and averages those
|
||||
* measurements over an interval period. Averaged measurement values are applied to the tuner controller
|
||||
* and broadcast as a TunerEvent for visual display.
|
||||
*/
|
||||
public class TunerFrequencyErrorMonitor implements Listener<SourceEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerFrequencyErrorMonitor.class);
|
||||
private static final int PROCESSING_INTERVAL_SECONDS = 5;
|
||||
private LinkedTransferQueue<Integer> mMeasurementsQueue = new LinkedTransferQueue<>();
|
||||
private List<Integer> mProcessingMeasurements = new ArrayList();
|
||||
private ScheduledFuture<?> mTimerHandle;
|
||||
private Tuner mTuner;
|
||||
|
||||
/**
|
||||
* Constructs a monitor for the tuner argument.
|
||||
* @param tuner to monitor for frequency error measurements
|
||||
*/
|
||||
public TunerFrequencyErrorMonitor(Tuner tuner)
|
||||
{
|
||||
mTuner = tuner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for receiving frequency error measurements from downstream decoders.
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED)
|
||||
{
|
||||
mMeasurementsQueue.add(sourceEvent.getValue().intValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average frequency error measurement value from discrete measurements received from
|
||||
* the decoders over the interval period.
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
mMeasurementsQueue.drainTo(mProcessingMeasurements);
|
||||
|
||||
if(mProcessingMeasurements.size() > 0)
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
for(Integer measurement: mProcessingMeasurements)
|
||||
{
|
||||
sum += measurement;
|
||||
}
|
||||
|
||||
//Note: the measurements are the frequency correction being applied to each channel to compensate
|
||||
//for the error int the channel. So, we report the negated value as the current error measurement
|
||||
broadcast(-(sum / mProcessingMeasurements.size()));
|
||||
|
||||
mProcessingMeasurements.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(int averageError)
|
||||
{
|
||||
mTuner.getTunerController().setMeasuredFrequencyError(averageError);
|
||||
mTuner.broadcast(new TunerEvent(mTuner, TunerEvent.Event.UPDATE_MEASURED_FREQUENCY_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts this monitor processing frequency error measurements received from select channel decoders
|
||||
* and averaging those values over a 1 second interval for transmission as a tuner event.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mTimerHandle == null)
|
||||
{
|
||||
mTimerHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new Processor(), 0,
|
||||
PROCESSING_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this monitor
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mTimerHandle != null)
|
||||
{
|
||||
mTimerHandle.cancel(true);
|
||||
mTimerHandle = null;
|
||||
broadcast(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable implementation to invoke the process() method when the timer fires.
|
||||
*/
|
||||
public class Processor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
process();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.dsp.mixer.ComplexMixerFactory;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import java.util.Iterator;
|
||||
import org.slf4j.Logger;
|
||||
@@ -44,13 +45,15 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
//Maximum number of filled buffers for the blocking queue
|
||||
private static final int BUFFER_MAX_CAPACITY = 600;
|
||||
|
||||
private Dispatcher<T> mBufferDispatcher;
|
||||
private ComplexMixer mFrequencyCorrectionMixer;
|
||||
private IRealDecimationFilter mIDecimationFilter;
|
||||
private IRealDecimationFilter mQDecimationFilter;
|
||||
private final Dispatcher<T> mBufferDispatcher;
|
||||
private final ComplexMixer mFrequencyCorrectionMixer;
|
||||
private final IRealDecimationFilter mIDecimationFilter;
|
||||
private final IRealDecimationFilter mQDecimationFilter;
|
||||
private Listener<ComplexSamples> mSamplesListener;
|
||||
private double mChannelSampleRate;
|
||||
private long mTunerFrequency;
|
||||
private final double mChannelSampleRate;
|
||||
private long mChannelFrequency;
|
||||
private final long mFrequencyOffset;
|
||||
private long mFrequencyCorrection;
|
||||
|
||||
/**
|
||||
* Constructs a frequency translating and CIC decimating channel source.
|
||||
@@ -60,14 +63,16 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
* @param sampleRate of the incoming sample stream
|
||||
* @param channelSpecification for the requested channel.
|
||||
* @param threadName for the dispatcher
|
||||
* @param tunerFrequencyErrorManager to connect with the channel error manager
|
||||
* @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter
|
||||
* designer and the filter parameters.
|
||||
*/
|
||||
public HalfBandTunerChannelSource(Listener<SourceEvent> producerSourceEventListener, TunerChannel tunerChannel,
|
||||
double sampleRate, ChannelSpecification channelSpecification, String threadName)
|
||||
double sampleRate, ChannelSpecification channelSpecification, String threadName,
|
||||
TunerFrequencyErrorManager tunerFrequencyErrorManager)
|
||||
throws FilterDesignException
|
||||
{
|
||||
super(producerSourceEventListener, tunerChannel, threadName);
|
||||
super(producerSourceEventListener, tunerChannel, threadName, tunerFrequencyErrorManager);
|
||||
|
||||
int desiredDecimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate());
|
||||
int decimation = DecimationFilterFactory.getDecimationRate(desiredDecimation);
|
||||
@@ -81,9 +86,20 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
|
||||
//Setup the frequency mixer to the current source frequency
|
||||
mChannelSampleRate = sampleRate / (double)decimation;
|
||||
mTunerFrequency = tunerChannel.getFrequency();
|
||||
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
|
||||
mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(frequencyOffset, sampleRate);
|
||||
mChannelFrequency = tunerChannel.getFrequency();
|
||||
mFrequencyOffset = mChannelFrequency - getTunerChannel().getFrequency();
|
||||
mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(mFrequencyOffset, sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets and applies the channel decoder requested frequency correction.
|
||||
* @param correction requested by the downstream processors.
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyCorrection(long correction)
|
||||
{
|
||||
mFrequencyCorrection = correction;
|
||||
updateMixerFrequencyOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,7 +138,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
@Override
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mTunerFrequency = frequency;
|
||||
mChannelFrequency = frequency;
|
||||
updateMixerFrequencyOffset();
|
||||
}
|
||||
|
||||
@@ -132,7 +148,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
*/
|
||||
private void updateMixerFrequencyOffset()
|
||||
{
|
||||
long offset = mTunerFrequency - getTunerChannel().getFrequency();
|
||||
long offset = mChannelFrequency - getTunerChannel().getFrequency() + mFrequencyCorrection;
|
||||
mFrequencyCorrectionMixer.setFrequency(offset);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -62,7 +62,7 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
|
||||
List<Long> frequencies, ChannelSpecification channelSpecification,
|
||||
String preferredTuner, String threadName)
|
||||
{
|
||||
super(null, tunerChannelSource.getTunerChannel(), threadName);
|
||||
super(null, tunerChannelSource.getTunerChannel(), threadName, null);
|
||||
mTunerManager = tunerManager;
|
||||
mTunerChannelSource = tunerChannelSource;
|
||||
mTunerChannelSource.setSourceEventListener(mConsumerSourceEventAdapter);
|
||||
@@ -71,6 +71,16 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
|
||||
mPreferredTuner = preferredTuner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the channel decoder requested frequency correction.
|
||||
* @param correction requested by the downstream processors.
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyCorrection(long correction)
|
||||
{
|
||||
mTunerChannelSource.setFrequencyCorrection(correction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles this source to use the next frequency in the list. If no other frequencies are available,
|
||||
* because of frequency locking, ignore the rotate request.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,6 +20,8 @@
|
||||
package io.github.dsheirer.source.tuner.channel;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.dsp.mixer.ComplexMixer;
|
||||
import io.github.dsheirer.dsp.mixer.ComplexMixerFactory;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
@@ -41,6 +43,7 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
||||
private TunerController mTunerController;
|
||||
private Dispatcher<INativeBuffer> mBufferDispatcher;
|
||||
private Listener<ComplexSamples> mBufferListener;
|
||||
private ComplexMixer mFrequencyCorrectionMixer;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -53,10 +56,11 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
||||
public PassThroughChannelSource(Listener<SourceEvent> listener, TunerController tunerController,
|
||||
TunerChannel tunerChannel, String threadName)
|
||||
{
|
||||
super(listener, tunerChannel, threadName);
|
||||
super(listener, tunerChannel, threadName, tunerController.getTunerFrequencyErrorManager());
|
||||
mTunerController = tunerController;
|
||||
mBufferDispatcher = new Dispatcher<>(threadName, 50, getHeartbeatManager());
|
||||
mBufferDispatcher.setListener(new BufferProcessor());
|
||||
mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(0,0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,7 +86,13 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
||||
@Override
|
||||
protected void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Request to set sample rate: " + sampleRate);
|
||||
mFrequencyCorrectionMixer.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyCorrection(long correction)
|
||||
{
|
||||
mFrequencyCorrectionMixer.setFrequency(correction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,7 +126,16 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
||||
{
|
||||
try
|
||||
{
|
||||
mBufferListener.receive(iterator.next());
|
||||
ComplexSamples samples = iterator.next();
|
||||
|
||||
if(mFrequencyCorrectionMixer.hasFrequency())
|
||||
{
|
||||
mBufferListener.receive(mFrequencyCorrectionMixer.mix(samples));
|
||||
}
|
||||
else
|
||||
{
|
||||
mBufferListener.receive(samples);
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,8 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.frequency.ChannelFrequencyErrorManager;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -32,11 +34,12 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
|
||||
protected static final long HEARTBEAT_INTERVAL_MS = 100;
|
||||
private SourceEventListenerToProcessorAdapter mConsumerSourceEventListenerAdapter;
|
||||
private final SourceEventListenerToProcessorAdapter mConsumerSourceEventListenerAdapter;
|
||||
protected TunerChannel mTunerChannel;
|
||||
private Listener<SourceEvent> mProducerSourceEventListener;
|
||||
private Listener<SourceEvent> mConsumerSourceEventListener;
|
||||
protected String mThreadName;
|
||||
private final ChannelFrequencyErrorManager mChannelFrequencyErrorManager;
|
||||
|
||||
/**
|
||||
* Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality
|
||||
@@ -47,14 +50,26 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
* @param threadName for the channel's dispatcher
|
||||
*/
|
||||
public TunerChannelSource(Listener<SourceEvent> producerSourceEventListener, TunerChannel tunerChannel,
|
||||
String threadName)
|
||||
String threadName, TunerFrequencyErrorManager tunerErrorManager)
|
||||
{
|
||||
mProducerSourceEventListener = producerSourceEventListener;
|
||||
mTunerChannel = tunerChannel;
|
||||
mConsumerSourceEventListenerAdapter = new SourceEventListenerToProcessorAdapter(this);
|
||||
mThreadName = threadName;
|
||||
mChannelFrequencyErrorManager = new ChannelFrequencyErrorManager(this, tunerErrorManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a frequency correction value as requested by the downstream channel consumer. This is normally going to be
|
||||
* the ChannelFrequencyCorrectionManager using correction measurements provided by the FeedbackDecoder. The
|
||||
* correction process is implemented separately in each subclass extension to this base class.
|
||||
*
|
||||
* Note: this value is in addition to the PPM correction being applied at the tuner level.
|
||||
*
|
||||
* @param correction requested by the downstream processors.
|
||||
*/
|
||||
public abstract void setFrequencyCorrection(long correction);
|
||||
|
||||
@Override
|
||||
public long getFrequency()
|
||||
{
|
||||
@@ -101,6 +116,8 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
mChannelFrequencyErrorManager.start();
|
||||
|
||||
//Broadcast current frequency and sample rate so consumer can configure correctly
|
||||
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(this, getFrequency(), "Startup"));
|
||||
broadcastProducerSourceEvent(SourceEvent.startSampleStreamRequest(this));
|
||||
@@ -112,6 +129,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mChannelFrequencyErrorManager.stop();
|
||||
broadcastProducerSourceEvent(SourceEvent.stopSampleStreamRequest(this));
|
||||
}
|
||||
|
||||
@@ -156,14 +174,20 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//Ignore these raw frequency measurement errors. We're only interested in the measurements
|
||||
//that occur when the channel state indicates that we're sync-locked.
|
||||
//Ignore
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
case REQUEST_FREQUENCY_CORRECTION:
|
||||
//Rebroadcast this measurement event so the producer can process it
|
||||
|
||||
if(mChannelFrequencyErrorManager != null)
|
||||
{
|
||||
mChannelFrequencyErrorManager.receive(sourceEvent);
|
||||
}
|
||||
|
||||
sourceEvent.setSource(this);
|
||||
broadcastProducerSourceEvent(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_STATUS:
|
||||
case NOTIFICATION_FREQUENCY_ROTATION_FAILURE:
|
||||
case NOTIFICATION_FREQUENCY_ROTATION_SUCCESS:
|
||||
case NOTIFICATION_CHANNEL_POWER:
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.source.tuner.frequency;
|
||||
|
||||
import io.github.dsheirer.buffer.FloatAveragingBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ChannelFrequencyCorrectionStatusNotification;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Channel frequency error controller works in tandem with the TunerFrequencyErrorController to track and send error
|
||||
* reports from the decoder and apply an oscillator to remove the tracked error incrementally over time.
|
||||
*/
|
||||
public class ChannelFrequencyErrorManager implements ISourceEventListener, Listener<SourceEvent>
|
||||
{
|
||||
private static final long MAXIMUM_CHANNEL_ERROR_CORRECTION_PER_INTERVAL = 10; //Hertz
|
||||
private static final long TIMER_INTERVAL_MILLISECONDS = 500;
|
||||
private final FloatAveragingBuffer mRequestedCorrectAveragingBuffer = new FloatAveragingBuffer(6, 2);
|
||||
private final TunerChannelSource mTunerChannelSource;
|
||||
private final TunerFrequencyErrorManager mParent;
|
||||
private ScheduledFuture<?> mScheduledFuture;
|
||||
private long mAppliedFrequencyCorrection = 0;
|
||||
private long mAverageRequestedCorrection = 0;
|
||||
private boolean mCurrentForTunerProcessing = false;
|
||||
private boolean mCurrentForChannelProcessing = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param tunerChannelSource to receive frequency correction requests
|
||||
* @param parent to register for tuner frequency correction events
|
||||
*/
|
||||
public ChannelFrequencyErrorManager(TunerChannelSource tunerChannelSource, TunerFrequencyErrorManager parent)
|
||||
{
|
||||
mTunerChannelSource = tunerChannelSource;
|
||||
mParent = parent;
|
||||
|
||||
if(mParent != null)
|
||||
{
|
||||
mParent.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of the current applied frequency correction for the channel.
|
||||
* @return total frequency error in Hertz.
|
||||
*/
|
||||
public long getFrequencyError()
|
||||
{
|
||||
return mAppliedFrequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a notification from the parent tuner is updating the PPM and frequency correction change value.
|
||||
*
|
||||
* Note: on receiving this change notification, the tuner manager will expose the current PPM and correction values.
|
||||
* @param change in Hertz resulting from the tuner's PPM update.
|
||||
*/
|
||||
public void receive(long change)
|
||||
{
|
||||
//The TunerFrequencyErrorManager notifies this channel of the correction applied at the tuner
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive error measurements from the channel demodulator and average them and update the actual frequency error
|
||||
* so that when the timer fires, it sends a correction update to the channel source to incrementally drive the
|
||||
* difference between the actual and corrected frequency error values.
|
||||
* @param sourceEvent from the FeedbackDecoder.
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
if(sourceEvent.getEvent().equals(SourceEvent.Event.REQUEST_FREQUENCY_CORRECTION))
|
||||
{
|
||||
long requestedCorrection = sourceEvent.getValue().longValue();
|
||||
mAverageRequestedCorrection = (long) mRequestedCorrectAveragingBuffer.get(requestedCorrection);
|
||||
mCurrentForChannelProcessing = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this manager has received an error measurement from the decoder since the last time the current
|
||||
* flag was cleared, when the last error measurement was read.
|
||||
* @return true if there is current error value available.
|
||||
*/
|
||||
public boolean isCurrentForTunerProcessing()
|
||||
{
|
||||
return mCurrentForTunerProcessing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current flag once the error value is read
|
||||
*/
|
||||
public void clearCurrentFlag()
|
||||
{
|
||||
mCurrentForTunerProcessing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the frequency error and sends an updated correction request to the TunerChannelSource. This method
|
||||
* is invoked on a timer so that it is independent of the decoder and sync pattern rate.
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
if(mCurrentForChannelProcessing)
|
||||
{
|
||||
long partialCorrection = Math.clamp(mAverageRequestedCorrection, -MAXIMUM_CHANNEL_ERROR_CORRECTION_PER_INTERVAL, MAXIMUM_CHANNEL_ERROR_CORRECTION_PER_INTERVAL);
|
||||
|
||||
if(partialCorrection != 0)
|
||||
{
|
||||
mAppliedFrequencyCorrection += partialCorrection;
|
||||
mTunerChannelSource.setFrequencyCorrection(mAppliedFrequencyCorrection);
|
||||
mRequestedCorrectAveragingBuffer.reset();
|
||||
}
|
||||
|
||||
ChannelFrequencyCorrectionStatusNotification notification = ChannelFrequencyCorrectionStatusNotification
|
||||
.create(mTunerChannelSource, mAverageRequestedCorrection, mAppliedFrequencyCorrection, mParent.getTunerPPM(),
|
||||
mParent.getTunerFrequencyCorrection(), mParent.isEnabled());
|
||||
mTunerChannelSource.broadcastConsumerSourceEvent(notification);
|
||||
mAverageRequestedCorrection = 0;
|
||||
mCurrentForTunerProcessing = true;
|
||||
mCurrentForChannelProcessing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ChannelFrequencyCorrectionStatusNotification notification = ChannelFrequencyCorrectionStatusNotification
|
||||
.create(mTunerChannelSource, 0, mAppliedFrequencyCorrection, mParent.getTunerPPM(),
|
||||
mParent.getTunerFrequencyCorrection(), mParent.isEnabled());
|
||||
mTunerChannelSource.broadcastConsumerSourceEvent(notification);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the timer processing of frequency error correction.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mParent != null)
|
||||
{
|
||||
mParent.add(this);
|
||||
|
||||
if(mScheduledFuture == null)
|
||||
{
|
||||
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::process, TIMER_INTERVAL_MILLISECONDS,
|
||||
TIMER_INTERVAL_MILLISECONDS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the timer processing of frequency error correction.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
}
|
||||
|
||||
if(mParent != null)
|
||||
{
|
||||
mParent.remove(this);
|
||||
}
|
||||
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -331,7 +331,7 @@ public class FrequencyController
|
||||
/**
|
||||
* Broadcast a frequency error/correction value change
|
||||
*/
|
||||
protected void broadcastFrequencyCorrectionChange() throws SourceException
|
||||
public void broadcastFrequencyCorrectionChange() throws SourceException
|
||||
{
|
||||
broadcast(SourceEvent.frequencyCorrectionChange((int)mFrequencyCorrection));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.source.tuner.frequency;
|
||||
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Controls tuner frequency error when Auto-PPM is enabled. Works in tandem with the ChannelFrequencyErrorController
|
||||
* to receive error measurement feedback from each channel's decoder and calculate an average frequency error across
|
||||
* all channels and adjusts the tuner's PPM setting to compensate for tuner frequency drift over time.
|
||||
*/
|
||||
public class TunerFrequencyErrorManager implements ISourceEventProcessor
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TunerFrequencyErrorManager.class);
|
||||
private static final double PPM_DIVISOR = 1.0 / 1_000_000.0;
|
||||
private static final long MAXIMUM_TUNER_ERROR_CORRECTION_PER_INTERVAL_HZ = 10;
|
||||
private static final long MINIMUM_CORRECTION_THRESHOLD_HZ = 50;
|
||||
private static final long TIMER_INTERVAL_SECONDS = 5;
|
||||
private static final DecimalFormat DF = new DecimalFormat("0.00000");
|
||||
private final List<ChannelFrequencyErrorManager> mChannelManagers = new ArrayList<>();
|
||||
private final TunerController mTunerController;
|
||||
private ScheduledFuture<?> mScheduledFuture;
|
||||
private boolean mEnabled = true;
|
||||
private boolean mShutdown = false;
|
||||
private double mTunerPPM;
|
||||
private long mTunerCorrection;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param tunerController to receive automatic PPM adjustments.
|
||||
*/
|
||||
public TunerFrequencyErrorManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
//Register to receive frequency and PPM change notifications from the tuner
|
||||
mTunerController.addListener(this);
|
||||
mTunerPPM = mTunerController.getFrequencyCorrection();
|
||||
mTunerCorrection = (long) (mTunerController.getFrequency() * mTunerPPM * PPM_DIVISOR);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mShutdown = true;
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(SourceEvent event) throws SourceException
|
||||
{
|
||||
//Receive tuner controller frequency change events to detect when the PPM has changed
|
||||
if(event.getEvent().equals(SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE))
|
||||
{
|
||||
double ppm = mTunerController.getFrequencyCorrection();
|
||||
|
||||
if(ppm != mTunerPPM)
|
||||
{
|
||||
double adjustment = ppm - mTunerPPM;
|
||||
long changeHz = (long)(mTunerController.getFrequency() * adjustment * PPM_DIVISOR);
|
||||
broadcast(changeHz);
|
||||
mTunerPPM = ppm;
|
||||
mTunerCorrection = (long)(mTunerController.getFrequency() * PPM_DIVISOR * mTunerPPM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the enabled state of this manager. When enabled, applies PPM corrections to the tuner controller.
|
||||
* @param enabled
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if tuner Auto-PPM is enabled.
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes channel frequency error values and updates the tuner to apply a correction.
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(!mChannelManagers.isEmpty())
|
||||
{
|
||||
long requestedChangeHz = 0;
|
||||
int count = 0;
|
||||
|
||||
for(ChannelFrequencyErrorManager manager : mChannelManagers)
|
||||
{
|
||||
if(manager.isCurrentForTunerProcessing())
|
||||
{
|
||||
requestedChangeHz += manager.getFrequencyError();
|
||||
count++;
|
||||
manager.clearCurrentFlag();
|
||||
}
|
||||
}
|
||||
|
||||
requestedChangeHz /= count;
|
||||
mTunerController.setMeasuredFrequencyError((int)requestedChangeHz);
|
||||
|
||||
if(Math.abs(requestedChangeHz) > MINIMUM_CORRECTION_THRESHOLD_HZ)
|
||||
{
|
||||
requestedChangeHz = Math.clamp(requestedChangeHz, -MAXIMUM_TUNER_ERROR_CORRECTION_PER_INTERVAL_HZ, MAXIMUM_TUNER_ERROR_CORRECTION_PER_INTERVAL_HZ);
|
||||
|
||||
if(mEnabled)
|
||||
{
|
||||
double adjustment = requestedChangeHz / (mTunerController.getFrequency() * PPM_DIVISOR);
|
||||
|
||||
try
|
||||
{
|
||||
mTunerController.setFrequencyCorrection(mTunerController.getFrequencyCorrection() + adjustment);
|
||||
}
|
||||
catch(SourceException e)
|
||||
{
|
||||
LOG.error("Error while adjusting PPM value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mTunerController.getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner frequency error correction in parts-per-million (ppm).
|
||||
*/
|
||||
public double getTunerPPM()
|
||||
{
|
||||
return mTunerController.getFrequencyCorrection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current tuner frequency correction value.
|
||||
* @return correction in Hertz
|
||||
*/
|
||||
public long getTunerFrequencyCorrection()
|
||||
{
|
||||
return mTunerCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies each of the channel managers of the amount of frequency change resulting from a PPM automatic adjustment
|
||||
* or a PPM manual adjustment.
|
||||
* @param change (ie correction) in Hertz that was applied at the tuner
|
||||
*/
|
||||
private void broadcast(long change)
|
||||
{
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
for(ChannelFrequencyErrorManager channelManager: mChannelManagers)
|
||||
{
|
||||
channelManager.receive(change);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mTunerController.getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the channel manager to receive and provide error updates and automatically starts the timer thread
|
||||
* if it is not already running.
|
||||
* @param channelFrequencyErrorManager to add
|
||||
*/
|
||||
public void add(ChannelFrequencyErrorManager channelFrequencyErrorManager)
|
||||
{
|
||||
boolean startRequired = false;
|
||||
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(!mChannelManagers.contains(channelFrequencyErrorManager))
|
||||
{
|
||||
mChannelManagers.add(channelFrequencyErrorManager);
|
||||
startRequired = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mTunerController.getLock().unlock();
|
||||
}
|
||||
|
||||
if(startRequired)
|
||||
{
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the channel error manager and stops the timer thread if all channels are removed.
|
||||
* @param channelFrequencyErrorManager to remove
|
||||
*/
|
||||
public void remove(ChannelFrequencyErrorManager channelFrequencyErrorManager)
|
||||
{
|
||||
boolean stopRequired = false;
|
||||
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
mChannelManagers.remove(channelFrequencyErrorManager);
|
||||
stopRequired = mChannelManagers.isEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
mTunerController.getLock().unlock();
|
||||
}
|
||||
|
||||
if(stopRequired)
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the timer processing of frequency error correction.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(!mShutdown && mScheduledFuture == null)
|
||||
{
|
||||
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::process, TIMER_INTERVAL_SECONDS,
|
||||
TIMER_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the timer processing of frequency error correction.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mScheduledFuture != null)
|
||||
{
|
||||
mScheduledFuture.cancel(true);
|
||||
}
|
||||
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import java.util.SortedSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -90,6 +91,12 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
|
||||
*/
|
||||
public abstract void setErrorMessage(String errorMessage);
|
||||
|
||||
/**
|
||||
* Access the tuner's frequency error manager to insert into each allocated tuner channel source to receive
|
||||
* feedback from the channel's decoder about measured signal error.
|
||||
*/
|
||||
public abstract TunerFrequencyErrorManager getTunerFrequencyErrorManager();
|
||||
|
||||
/**
|
||||
* Adds a listener to receive source events
|
||||
*/
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.manager;
|
||||
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import java.text.DecimalFormat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Monitors measured frequency error PPM values from the tuner and (when enabled) applies the current
|
||||
* error measurement to the tuner controller to adjust the tuner's PPM and align the frequency value.
|
||||
*/
|
||||
public class FrequencyErrorCorrectionManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FrequencyErrorCorrectionManager.class);
|
||||
|
||||
private static final double FREQUENCY_CORRECTION_ERROR_THRESHOLD = 0.4;
|
||||
private static final long AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS = 30 * 1000; //30 seconds
|
||||
private static final long INITIAL_AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS = 5 * 1000; //5 seconds
|
||||
private long mObservationPeriodStart;
|
||||
private double mPPMRequired;
|
||||
private boolean mEnabled = true;
|
||||
private DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
|
||||
private TunerController mTunerController;
|
||||
|
||||
/**
|
||||
* Constructs a frequency error correction manager for the specified tuner controller.
|
||||
*/
|
||||
public FrequencyErrorCorrectionManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this manager instance and nullify references.
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
setEnabled(false);
|
||||
mTunerController = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables automatic frequency PPM correction.
|
||||
* @param enabled to true to turn on automatic PPM adjustments.
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if Auto-PPM is enabled
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets monitoring
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mObservationPeriodStart = 0;
|
||||
mPPMRequired = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a PPM adjustment value to the tuner's current PPM setting, when auto-correction is enabled.
|
||||
*/
|
||||
private void applyCorrection()
|
||||
{
|
||||
if(mEnabled && mTunerController != null && mTunerController.hasMeasuredFrequencyError())
|
||||
{
|
||||
double frequencyCorrection = mTunerController.getFrequencyCorrection();
|
||||
frequencyCorrection -= mTunerController.getPPMFrequencyError();
|
||||
|
||||
try
|
||||
{
|
||||
mLog.info("Auto-Correcting Tuner PPM to [" + mDecimalFormat.format(frequencyCorrection) + "]");
|
||||
mTunerController.setFrequencyCorrection(frequencyCorrection);
|
||||
reset();
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error while applying automatic frequency error PPM correction value: " + frequencyCorrection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the monitor with a new PPM frequency error measurement from the tuner.
|
||||
*/
|
||||
public void updatePPM(double ppm)
|
||||
{
|
||||
if(ppm > FREQUENCY_CORRECTION_ERROR_THRESHOLD)
|
||||
{
|
||||
if(mObservationPeriodStart == 0)
|
||||
{
|
||||
mObservationPeriodStart = System.currentTimeMillis() - AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS +
|
||||
INITIAL_AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS;
|
||||
mPPMRequired = ppm;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mPPMRequired > 0)
|
||||
{
|
||||
mPPMRequired = ppm;
|
||||
|
||||
if(mObservationPeriodStart + AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS < System.currentTimeMillis())
|
||||
{
|
||||
applyCorrection();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Opposite polarity ppm value ... restart the timer
|
||||
mObservationPeriodStart = System.currentTimeMillis();
|
||||
mPPMRequired = ppm;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(ppm < -FREQUENCY_CORRECTION_ERROR_THRESHOLD)
|
||||
{
|
||||
if(mObservationPeriodStart == 0)
|
||||
{
|
||||
mObservationPeriodStart = System.currentTimeMillis();
|
||||
mPPMRequired = ppm;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mPPMRequired < 0)
|
||||
{
|
||||
mPPMRequired = ppm;
|
||||
|
||||
if(mObservationPeriodStart + AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS < System.currentTimeMillis())
|
||||
{
|
||||
applyCorrection();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Opposite polarity ppm value ... restart the timer
|
||||
mObservationPeriodStart = System.currentTimeMillis();
|
||||
mPPMRequired = ppm;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -30,6 +30,7 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
@@ -54,12 +55,22 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
private NativeSampleDelayBuffer mSampleDelayBuffer;
|
||||
private boolean mRunning = true;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param tunerController for the tuner
|
||||
*/
|
||||
public HeterodyneChannelSourceManager(TunerController tunerController)
|
||||
{
|
||||
mTunerController = tunerController;
|
||||
mTunerController.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerFrequencyErrorManager getTunerFrequencyErrorManager()
|
||||
{
|
||||
return mTunerController.getTunerFrequencyErrorManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStateDescription()
|
||||
{
|
||||
@@ -114,40 +125,39 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
|
||||
TunerChannelSource source = null;
|
||||
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
mTunerController.getLock().lock();
|
||||
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
|
||||
{
|
||||
try
|
||||
{
|
||||
//Attempt to create the channel source first, in case we get a filter design exception
|
||||
HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor,
|
||||
tunerChannel, mTunerController.getSampleRate(), channelSpecification, threadName);
|
||||
//Attempt to create the channel source first, in case we get a filter design exception
|
||||
HalfBandTunerChannelSource<?> tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor,
|
||||
tunerChannel, mTunerController.getSampleRate(), channelSpecification, threadName,
|
||||
getTunerFrequencyErrorManager());
|
||||
|
||||
//Add to the list of channel sources so that it will receive the tuner frequency change
|
||||
mChannelSources.add(tunerChannelSource);
|
||||
//Add to the list of channel sources so that it will receive the tuner frequency change
|
||||
mChannelSources.add(tunerChannelSource);
|
||||
|
||||
//Set the current tuner frequency
|
||||
tunerChannelSource.setFrequency(mTunerController.getFrequency());
|
||||
//Set the current tuner frequency
|
||||
tunerChannelSource.setFrequency(mTunerController.getFrequency());
|
||||
|
||||
//Add to the channel list and update the tuner center frequency as needed
|
||||
mTunerChannels.add(tunerChannel);
|
||||
updateTunerFrequency();
|
||||
//Add to the channel list and update the tuner center frequency as needed
|
||||
mTunerChannels.add(tunerChannel);
|
||||
updateTunerFrequency();
|
||||
|
||||
//Lock the tuner controller frequency and sample rate
|
||||
mTunerController.setLockedSampleRate(true);
|
||||
//Lock the tuner controller frequency and sample rate
|
||||
mTunerController.setLockedSampleRate(true);
|
||||
|
||||
broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
|
||||
broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
|
||||
|
||||
source = tunerChannelSource;
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Error creating CIC tuner channel source - couldn't design cleanup filter", fde);
|
||||
}
|
||||
source = tunerChannelSource;
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Error creating CIC tuner channel source - couldn't design cleanup filter", fde);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mTunerController.getLock().unlock();
|
||||
@@ -216,6 +226,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
|
||||
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//no-op
|
||||
break;
|
||||
default:
|
||||
@@ -328,13 +339,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast so that the tuner source can process this event
|
||||
broadcast(sourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_CHANNEL_COUNT_CHANGE:
|
||||
//Lock the tuner controller frequency & sample rate when we're processing channels
|
||||
break;
|
||||
case REQUEST_FREQUENCY_CORRECTION:
|
||||
//Ignore
|
||||
break;
|
||||
default:
|
||||
mLog.info("Unrecognized Source Event received from channel: " + sourceEvent);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -29,6 +29,7 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.PassThroughChannelSource;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
@@ -67,6 +68,12 @@ public class PassThroughSourceManager extends ChannelSourceManager
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerFrequencyErrorManager getTunerFrequencyErrorManager()
|
||||
{
|
||||
return mTunerController.getTunerFrequencyErrorManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAllChannels()
|
||||
{
|
||||
@@ -104,9 +111,10 @@ public class PassThroughSourceManager extends ChannelSourceManager
|
||||
|
||||
TunerChannelSource source = null;
|
||||
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
mTunerController.getLock().lock();
|
||||
PassThroughChannelSource channelSource = new PassThroughChannelSource(new SourceEventProxy(),
|
||||
mTunerController, tunerChannel, threadName);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.frequency.TunerFrequencyErrorManager;
|
||||
import java.util.SortedSet;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
@@ -38,8 +39,8 @@ import org.slf4j.LoggerFactory;
|
||||
public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSourceManager.class);
|
||||
private PolyphaseChannelManager mPolyphaseChannelManager;
|
||||
private TunerController mTunerController;
|
||||
private final PolyphaseChannelManager mPolyphaseChannelManager;
|
||||
private final TunerController mTunerController;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -55,6 +56,12 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
mTunerController.addListener(mPolyphaseChannelManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerFrequencyErrorManager getTunerFrequencyErrorManager()
|
||||
{
|
||||
return mTunerController.getTunerFrequencyErrorManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStateDescription()
|
||||
{
|
||||
@@ -429,9 +436,10 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
{
|
||||
TunerChannelSource tunerChannelSource = null;
|
||||
|
||||
mTunerController.getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
mTunerController.getLock().lock();
|
||||
if(isTunable(tunerChannel))
|
||||
{
|
||||
//Get a new set of currently tuned channels
|
||||
@@ -500,11 +508,6 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
|
||||
//when the polyphase manager has channels allocated
|
||||
mTunerController.setLockedSampleRate(getTunerChannelCount() > 0);
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast these frequency measurement errors to the tuner and tuner controller
|
||||
//for display and/or correction
|
||||
broadcast(sourceEvent);
|
||||
break;
|
||||
default:
|
||||
mLog.info("Unrecognized source event: " + sourceEvent);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -246,7 +246,7 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
|
||||
if(tuner != null)
|
||||
{
|
||||
boolean enabled = getAutoPPMCheckBox().isSelected();
|
||||
getTuner().getTunerController().getFrequencyErrorCorrectionManager().setEnabled(enabled);
|
||||
getTuner().getTunerController().getTunerFrequencyErrorManager().setEnabled(enabled);
|
||||
save();
|
||||
}
|
||||
}
|
||||
@@ -917,7 +917,7 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
|
||||
getMinimumFrequencyTextField().setFrequency(tuner.getTunerController().getMinimumFrequency());
|
||||
getMaximumFrequencyTextField().setFrequency(tuner.getTunerController().getMaximumFrequency());
|
||||
getFrequencyCorrectionSpinner().setValue(tuner.getTunerController().getFrequencyCorrection());
|
||||
getAutoPPMCheckBox().setSelected(tuner.getTunerController().getFrequencyErrorCorrectionManager().isEnabled());
|
||||
getAutoPPMCheckBox().setSelected(tuner.getTunerController().getTunerFrequencyErrorManager().isEnabled());
|
||||
getFrequencyControl().addListener(getTuner().getTunerController());
|
||||
getTuner().getTunerController().addListener(getFrequencyControl());
|
||||
getMeasuredPPMLabel().setText(tuner.getTunerController().getMeasuredErrorStatus());
|
||||
|
||||
Reference in New Issue
Block a user