#1397 Resolves issue where decoding channels go IDLE after period of time. Isolated the cause to a multi-threading issue where the polyphase channel manager receives notification that the tuner center frequency has changed and sets a flag to update each of the channels. This happens concurrent with a previous tuner center frequency change where the flag gets set and the second thread is updating the output processors with the previous frequency change and then unsets the frequency change flag, completely missing the second/latest frequency change update and causing the channels to all tune-out their desired signals as the tuner center frequency changes and the output channels continue mixing to the previous frequency. (#1407)

Identified and resolved issue where a 2-stream polyphase synthesizer was being updated with an incorrectly designed synthesis filter which was causing massive droop on the second half of the reconstructed channel.  Oddly enough, this didn't seem to impact the P25 decoders, but it may have impacted other decoding.

Identified and resolved issue with concurrent modification of the modules list in the processing chain associated with recent addition of the channel FFT view in the channel power tab.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2023-01-15 04:12:42 -05:00
committed by GitHub
parent f97b2150b9
commit f0db2865f2
18 changed files with 412 additions and 99 deletions

View File

@@ -1,31 +1,34 @@
/*******************************************************************************
* *********************************************************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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 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.
* 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/>
* *********************************************************************************************************************
******************************************************************************/
* 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.dsp.filter.channelizer;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class ChannelCalculator
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
/**
@@ -546,13 +549,13 @@ public class ChannelCalculator
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Channel Calculator Settings");
sb.append(" Channels:").append(getChannelCount());
sb.append(" Sample Rate:").append(getSampleRate());
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
sb.append(" Channel Rate:").append(getChannelSampleRate());
sb.append(" Min:").append(getMinimumFrequency());
sb.append(" Max:").append(getMaximumFrequency());
sb.append("Channel Calculator | Tuner SR:").append(FREQUENCY_FORMAT.format(getSampleRate() / 1E6D));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(getCenterFrequency() / 1E6D));
sb.append(" MIN:").append(FREQUENCY_FORMAT.format(getMinimumFrequency() / 1E6D));
sb.append(" MAX:").append(FREQUENCY_FORMAT.format(getMaximumFrequency() / 1E6D));
sb.append(" | Channel COUNT:").append(getChannelCount());
sb.append(" BW:").append(FREQUENCY_FORMAT.format(getChannelBandwidth() / 1E6D));
sb.append(" SR:").append(FREQUENCY_FORMAT.format(getChannelSampleRate() / 1E6D));
return sb.toString();
}
}

View File

@@ -35,6 +35,7 @@ import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.util.Dispatcher;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -43,6 +44,7 @@ import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,6 +67,7 @@ import org.slf4j.LoggerFactory;
*/
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
@@ -123,6 +126,34 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
}
/**
* Provides a description of the state of this manager.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel Manager Providing [").append(mChannelSources.size()).append("] Channels");
sb.append("\n\t").append(mChannelCalculator);
for(PolyphaseChannelSource pcs: mChannelSources)
{
List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sampleRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
sb.append("\n\tPolyphase | Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
sb.append(" BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
sb.append(" | Channel CF: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
sb.append(" | Polyphase Indices: ").append(indexes);
}
return sb.toString();
}
public void stopAllChannels()
{
mRunning = false;
@@ -278,7 +309,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//no-op
break;
default:
mLog.info("Unrecognized source event: " + sourceEvent.toString());
mLog.info("Unrecognized source event: " + sourceEvent);
break;
}
}
@@ -442,16 +473,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
/**
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
*
* This monitor incorporates a queue for receiving source events that affect configuration of the channelizer so
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
* and/or the channel sources and output processors.
* This monitor incorporates a source event handler that queues a center frequency update so that it can be
* handled on the buffer processing thread, avoiding having to lock on the output processor thread. Since we
* anticipate that these two threads will contend for access to this update required flag, we use an update lock
* to protect access to the flag.
*/
public class NativeBufferReceiver implements Listener<INativeBuffer>
{
private boolean mOutputProcessorUpdateRequired = false;
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
/**
* Queues the source event for deferred execution on the buffer processing thread.
* Processes tuner center frequency change source events to flag when output processors need updating.
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
*/
public void receive(SourceEvent event)
@@ -460,14 +493,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mChannelCalculator.getCenterFrequency() != frequency)
{
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
mUpdateFlagLock.lock();
try
{
//Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
}
finally
{
mUpdateFlagLock.unlock();
}
}
}
/**
* Process native buffer streams and update polyphase output channels when the parent tuner center
* frequency changes.
* @param nativeBuffer of sample to process.
*/
@Override
public void receive(INativeBuffer nativeBuffer)
{
mUpdateFlagLock.lock();
try
{
if(mOutputProcessorUpdateRequired)
@@ -475,21 +525,28 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
updateOutputProcessors();
mOutputProcessorUpdateRequired = false;
}
}
finally
{
mUpdateFlagLock.unlock();
}
if(mPolyphaseChannelizer != null)
if(mPolyphaseChannelizer != null)
{
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
{
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
try
{
mPolyphaseChannelizer.receive(iterator.next());
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
}
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
}
}
}

View File

@@ -71,6 +71,14 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
updateOutputProcessor(channelCalculator, filterManager);
}
/**
* Description of the state or configuration of the output processor.
*/
public String getStateDescription()
{
return mPolyphaseChannelOutputProcessor.getStateDescription();
}
/**
* Current output processor indexes.
* @return indexes
@@ -174,7 +182,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
try
{
float[] filter = filterManager.getFilter(channelCalculator.getSampleRate(),
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size());
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -19,6 +19,7 @@
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
@@ -65,6 +66,20 @@ public class TwoChannelSynthesizerM2
init(filter);
}
/**
* Description of the state/configuration of this synthesizer to support debugging.
* @return state description.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two-Channel Synthesizer");
sb.append("\n\t\t Interleaved Filter: " + Arrays.toString(mIQInterleavedFilter));
sb.append("\n\t\t Serpentine Buffer Length: ").append(mSerpentineDataBuffer.length);
sb.append("\n\t\t Filter Vector Product Length: ").append(mFilterVectorProduct.length);
return sb.toString();
}
/**
* Initializes the synthesizer filter and data buffers for operation.
*
@@ -73,7 +88,6 @@ public class TwoChannelSynthesizerM2
private void init(float[] filter)
{
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
mFilterVectorProduct = new float[mIQInterleavedFilter.length];

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -24,6 +24,11 @@ import java.util.List;
public interface IPolyphaseChannelOutputProcessor
{
/**
* Description of the state/configuration of this output processor
*/
String getStateDescription();
/**
* Start processing channel results
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -45,6 +45,16 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
mMixerAssembler.getMixer().setSampleRate(sampleRate);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("One Channel Output Processor");
sb.append("\n\tIndex (doubled) [").append(mChannelOffset).append("]");
sb.append("\n\tMixer Assembler: none");
return sb.toString();
}
@Override
public void setSynthesisFilter(float[] filter)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -42,6 +42,19 @@ public class TwoChannelMixerAssembler extends MixerAssembler
super(gain);
}
/**
* Description of the state/configuration of this mixer assembler.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two Channel Mixer Assembler - State Description:");
sb.append("\n\tTwo-Channel Synthesizer: ").append(mTwoChannelSynthesizer.getStateDescription());
sb.append("\n\tMixer Frequency: ").append(getMixer().getFrequency());
sb.append("\n\tGain: ").append(getGain().getGain());
return sb.toString();
}
/**
* Sets the filter coefficients to use for two-channel synthesis
* @param filter

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -49,6 +49,16 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
setSynthesisFilter(filter);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two Channel Output Processor");
sb.append("\n\tIndices (doubled) 1 [").append(mChannelOffset1).append("] 2 [").append(mChannelOffset2).append("]");
sb.append("\n\tMixer Assembler: ").append(mMixerAssembler.getStateDescription());
return sb.toString();
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -41,7 +41,7 @@ public abstract class ComplexGain
* Gain value that will be applied to complex samples
* @return gain value
*/
protected float getGain()
public float getGain()
{
return mGain;
}

View File

@@ -190,6 +190,7 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
mLog.info(sb.toString());
mLog.info("Output Processor: " + pcs.getStateDescription());
}
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
{

View File

@@ -74,9 +74,11 @@ import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -115,12 +117,15 @@ public class ProcessingChain implements Listener<ChannelEvent>
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private List<Module> mModules = new ArrayList<>();
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
private MessageHistory mMessageHistory = new MessageHistory(500);
private AbstractChannelState mChannelState;
private EventBus mEventBus;
protected Source mSource;
//Lock to protect access to the modules list.
private ReentrantLock mModuleLock = new ReentrantLock();
private List<Module> mModules = new ArrayList<>();
/**
* Creates a processing chain for managing a set of modules
@@ -195,30 +200,46 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public void removeTrafficChannelManager()
{
Iterator<Module> it = mModules.iterator();
mModuleLock.lock();
while(it.hasNext())
try
{
if(it.next() instanceof TrafficChannelManager)
Iterator<Module> it = mModules.iterator();
while(it.hasNext())
{
it.remove();
if(it.next() instanceof TrafficChannelManager)
{
it.remove();
}
}
}
finally
{
mModuleLock.unlock();
}
}
public void dispose()
{
stop();
List<Module> modules = new ArrayList<>(mModules);
mModuleLock.lock();
for(Module module : modules)
try
{
removeModule(module);
module.dispose();
}
List<Module> modulesToRemove = new ArrayList<>(mModules);
mModules.clear();
for(Module module : modulesToRemove)
{
removeModule(module);
module.dispose();
}
}
finally
{
mModuleLock.unlock();
}
mAudioSegmentBroadcaster.dispose();
mDecodeEventBroadcaster.dispose();
@@ -282,7 +303,7 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public List<Module> getModules()
{
return mModules;
return Collections.unmodifiableList(mModules);
}
/**
@@ -292,13 +313,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<DecoderState> decoderStates = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof DecoderState)
for(Module module : mModules)
{
decoderStates.add((DecoderState)module);
if(module instanceof DecoderState)
{
decoderStates.add((DecoderState)module);
}
}
}
finally
{
mModuleLock.unlock();
}
return decoderStates;
}
@@ -330,7 +360,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public void addModule(Module module)
{
mModules.add(module);
mModuleLock.lock();
try
{
mModules.add(module);
}
finally
{
mModuleLock.unlock();
}
module.setInterModuleEventBus(getEventBus());
registerListeners(module);
registerProviders(module);
@@ -345,7 +385,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
unregisterListeners(module);
unregisterProviders(module);
module.setInterModuleEventBus(null);
mModules.remove(module);
mModuleLock.lock();
try
{
mModules.remove(module);
}
finally
{
mModuleLock.unlock();
}
}
/**
@@ -664,18 +714,27 @@ public class ProcessingChain implements Listener<ChannelEvent>
+ "sample type - cannot start processing chain");
}
/* Start each of the modules */
for(Module module : mModules)
mModuleLock.lock();
try
{
try
/* Start each of the modules */
for(Module module : mModules)
{
module.start();
}
catch(Exception e)
{
mLog.error("Error starting module", e);
try
{
module.start();
}
catch(Exception e)
{
mLog.error("Error starting module", e);
}
}
}
finally
{
mModuleLock.unlock();
}
}
else
{
@@ -716,15 +775,28 @@ public class ProcessingChain implements Listener<ChannelEvent>
}
/* Stop each of the remaining modules */
for(Module module : mModules)
{
module.stop();
}
mModuleLock.lock();
//Reset each of the modules
for(Module module : mModules)
try
{
module.reset();
for(Module module : mModules)
{
module.stop();
}
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
}
catch(Exception e)
{
mLog.error("Error stopping or resetting modules", e);
}
finally
{
mModuleLock.unlock();
}
}
}
@@ -736,13 +808,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<Module> eventLoggingModules = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof EventLogger)
for(Module module : mModules)
{
eventLoggingModules.add(module);
if(module instanceof EventLogger)
{
eventLoggingModules.add(module);
}
}
}
finally
{
mModuleLock.unlock();
}
for(Module eventLoggingModule : eventLoggingModules)
{
@@ -757,21 +838,30 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<Module> recordingModules = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof ComplexSamplesWaveRecorder)
for(Module module : mModules)
{
recordingModules.add(module);
}
else if(module instanceof MBECallSequenceRecorder)
{
recordingModules.add(module);
}
else if(module instanceof BinaryRecorder)
{
recordingModules.add(module);
if(module instanceof ComplexSamplesWaveRecorder)
{
recordingModules.add(module);
}
else if(module instanceof MBECallSequenceRecorder)
{
recordingModules.add(module);
}
else if(module instanceof BinaryRecorder)
{
recordingModules.add(module);
}
}
}
finally
{
mModuleLock.unlock();
}
for(Module recordingModule : recordingModules)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -52,6 +52,12 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
*/
public abstract SortedSet<TunerChannel> getTunerChannels();
/**
* State of this channel source manager for logging and debug purposes
* @return description of the state or configuration of this manager
*/
public abstract String getStateDescription();
/**
* Count of tuner channels being sourced by this source manager.
* @return

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -55,6 +55,27 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
return mTunerStatus;
}
/**
* Logs current state of the tuner
*/
public void logState()
{
StringBuilder sb = new StringBuilder();
sb.append("Discovered Tuner: ").append(getId());
if(hasTuner())
{
sb.append("\n\tTuner - Frequency:").append(getTuner().getTunerController().getFrequency());
sb.append("\n\tChannel Manager:").append(getTuner().getChannelSourceManager().getStateDescription());
}
else
{
sb.append("\n\tTuner - no tuner");
}
mLog.info(sb.toString());
}
/**
* Sets the status of the discovered tuner and notifies registered listeners of the status change.
* @param tunerStatus to set

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -60,6 +60,23 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(this);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Heterodyne Channel Source Manager Providing [").append(mTunerChannels.size()).append("] Channels");
sb.append("\n\tTuner Controller Frequency: ").append(mTunerController.getFrequency());
for(HalfBandTunerChannelSource channelSource: mChannelSources)
{
sb.append("\n\tChannel [").append(channelSource.getTunerChannel())
.append("] Frequency [").append(channelSource.getFrequency())
.append("] Mixer [").append(channelSource.getMixerFrequency())
.append("]");
}
return sb.toString();
}
@Override
public void stopAllChannels()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -54,6 +54,19 @@ public class PassThroughSourceManager extends ChannelSourceManager
mTunerController = tunerController;
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Pass-Through Channel Source Manager Providing [").append(mTunerChannels.size()).append("] Channels");
for(TunerChannel channel: mTunerChannels)
{
sb.append("\n\tChannel: ").append(channel);
}
return sb.toString();
}
@Override
public void stopAllChannels()
{

View File

@@ -55,6 +55,17 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(mPolyphaseChannelManager);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel Source Manager");
sb.append("\n\tTuner Controller Frequency: ").append(mTunerController.getFrequency());
sb.append("\n\t").append(mPolyphaseChannelManager.getStateDescription());
return sb.toString();
}
@Override
public void stopAllChannels()
{
@@ -454,8 +465,13 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
}
catch(IllegalArgumentException iae)
{
mLog.error("Center frequency calculation failed", iae);
//Center frequency calculation failed
}
catch(Exception e)
{
mLog.error("Error getting source", e);
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -189,6 +189,7 @@ public class DiscoveredTunerModel extends AbstractTableModel implements Listener
*/
public void removeDiscoveredTuner(DiscoveredTuner discoveredTuner)
{
mLog.info("Removing discovered tuner: " + discoveredTuner.getId());
mLock.lock();
try

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -39,7 +39,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
@@ -115,6 +117,32 @@ public class TunerViewPanel extends JPanel
}
});
//Add support for right-click context menu to the tuner table
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem logStateMenuItem = new JMenuItem("Log Tuner State");
logStateMenuItem.addActionListener(e -> {
int viewRow = mTunerTable.getSelectedRow();
DiscoveredTuner selected = null;
if(viewRow >= 0)
{
int modelRow = mTunerTable.convertRowIndexToModel(viewRow);
selected = mDiscoveredTunerModel.getDiscoveredTuner(modelRow);
}
if(selected != null)
{
selected.logState();
}
else
{
mLog.error("Can't log state - tuner not selected");
}
});
popupMenu.add(logStateMenuItem);
mTunerTable.setComponentPopupMenu(popupMenu);
//Monitor for tuner removal events so we can update the editor when our selected tuner is removed
mDiscoveredTunerModel.addTableModelListener(e ->
{