Compare commits

...

8 Commits

Author SHA1 Message Date
Denny Sheirer
ea46a8b0e8 Merge pull request #225 from DSheirer/222-spectral-display-of-frequency-correction-offset
Frequency change event handling in the processing chain
2017-03-11 17:03:34 +00:00
Denny
d510c34356 Adds frequency change event listener handler to LSM decoder. 2017-03-11 12:01:45 -05:00
Denny
ae8a44caed Removes cic cleanup filter from processing chain. 2017-03-11 11:53:12 -05:00
Denny
1aa85a86ca Resolves issue with channel frequency correction events not being broadcast within the processing chain and rendering in the spectral display. 2017-03-11 10:16:08 -05:00
Denny Sheirer
17539d4d55 Merge pull request #224 from DSheirer/223-now-playing-frequency-format
Now Playing frequency display formatting
2017-03-11 11:14:58 +00:00
Denny
1db1d28390 Resolves issue with frequency display formating in the Now Playing window. 2017-03-11 06:13:59 -05:00
Denny Sheirer
37a941e904 Merge pull request #219 from DSheirer/218-audio-priority-
Resolves issue with audio playback allowing higher priority audio to preempt lower priority audio
2017-03-05 12:34:09 +00:00
Denny
57f6655824 Resolves issue where higher priority audio was not preempting lower priority audio. 2017-03-05 07:32:42 -05:00
22 changed files with 1787 additions and 1406 deletions

View File

@@ -86,6 +86,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
loadSettings(); loadSettings();
} }
/**
* Loads the saved mixer configuration or a default configuration for audio playback.
*/
private void loadSettings() private void loadSettings()
{ {
MixerChannelConfiguration configuration = null; MixerChannelConfiguration configuration = null;
@@ -132,6 +135,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
} }
/**
* Creates a default audio playback configuration with a mono audio playback channel.
*/
private MixerChannelConfiguration getDefaultConfiguration() private MixerChannelConfiguration getDefaultConfiguration()
{ {
/* Use the system default mixer and mono channel as default startup */ /* Use the system default mixer and mono channel as default startup */
@@ -161,6 +167,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mAudioOutputConnections.clear(); mAudioOutputConnections.clear();
} }
/**
* Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
*/
@Override @Override
public synchronized void receive(AudioPacket packet) public synchronized void receive(AudioPacket packet)
{ {
@@ -176,11 +185,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections) for(AudioOutputConnection connection : mAudioOutputConnections)
{ {
if(connection.isInactive() && if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
{ {
mChannelConnectionMap.remove(connection.getChannelMetadataID()); mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.disconnect(); connection.disconnect();
mAvailableConnectionCount++;
changed = true; changed = true;
} }
} }
@@ -192,7 +201,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
/** /**
* Identifies the lowest priority channel connection * Identifies the lowest priority channel connection where the a higher value indicates a lower priority.
*/ */
private void updateLowestPriorityAssignment() private void updateLowestPriorityAssignment()
{ {
@@ -201,19 +210,32 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections) for(AudioOutputConnection connection : mAudioOutputConnections)
{ {
if(connection.isConnected() && if(connection.isConnected() &&
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority())) (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
{ {
mLowestPriorityConnection = connection; mLowestPriorityConnection = connection;
} }
} }
} }
/**
* Configures audio playback to use the configuration specified in the entry argument.
*
* @param entry to use in configuring the audio playback setup.
* @throws AudioException if there is an error
*/
@Override @Override
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
{ {
setMixerChannelConfiguration(entry, true); setMixerChannelConfiguration(entry, true);
} }
/**
* Configures audio playback to use the configuration specified in the entry argument.
*
* @param entry to use in configuring the audio playback setup.
* @param saveSettings to save the audio playback configuration settings in the properties file.
* @throws AudioException if there is an error
*/
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
{ {
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO)) if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
@@ -266,9 +288,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
/** /**
* Clears all channel assignments and terminates all audio outputs in * Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
* preparation for complete shutdown or change to another mixer/channel * to another mixer/channel configuration
* configuration
*/ */
private void disposeCurrentConfiguration() private void disposeCurrentConfiguration()
{ {
@@ -288,12 +309,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mLowestPriorityConnection = null; mLowestPriorityConnection = null;
} }
/**
* Current audio playback mixer channel configuration setting.
*/
@Override @Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{ {
return mMixerChannelConfiguration; return mMixerChannelConfiguration;
} }
/**
* List of audio outputs available for the current mixer channel configuration
*/
@Override @Override
public List<AudioOutput> getAudioOutputs() public List<AudioOutput> getAudioOutputs()
{ {
@@ -311,12 +338,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
return outputs; return outputs;
} }
/**
* Adds an audio event listener to receive audio event notifications.
*/
@Override @Override
public void addControllerListener(Listener<AudioEvent> listener) public void addControllerListener(Listener<AudioEvent> listener)
{ {
mControllerBroadcaster.addListener(listener); mControllerBroadcaster.addListener(listener);
} }
/**
* Removes an audio event listener from receiving audio event notifications.
*/
@Override @Override
public void removeControllerListener(Listener<AudioEvent> listener) public void removeControllerListener(Listener<AudioEvent> listener)
{ {
@@ -324,16 +357,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
} }
/** /**
* Returns an audio output connection for the packet if one is * Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
* available, or overrides an existing lower priority connection. * connection. Returns null if no connection is available for the audio packet.
* Returns null if no connection is available for the audio packet.
* *
* @param packet from a source * @param audioPacket from a decoding channel source
* @return an audio output connection or null * @return an audio output connection or null
*/ */
private AudioOutputConnection getConnection(AudioPacket packet) private AudioOutputConnection getConnection(AudioPacket audioPacket)
{ {
int channelMetadataID = packet.getMetadata().getMetadataID(); int channelMetadataID = audioPacket.getMetadata().getMetadataID();
//Use an existing connection //Use an existing connection
if(mChannelConnectionMap.containsKey(channelMetadataID)) if(mChannelConnectionMap.containsKey(channelMetadataID))
@@ -341,37 +373,35 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
return mChannelConnectionMap.get(channelMetadataID); return mChannelConnectionMap.get(channelMetadataID);
} }
//Connect to and use an available connection //Connect to an unused, available connection
if(mAvailableConnectionCount > 0) if(mAvailableConnectionCount > 0)
{ {
for(AudioOutputConnection connection : mAudioOutputConnections) for(AudioOutputConnection connection : mAudioOutputConnections)
{ {
if(connection.isDisconnected()) if(connection.isDisconnected())
{ {
connection.connect(channelMetadataID); connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
mChannelConnectionMap.put(channelMetadataID, connection); mChannelConnectionMap.put(channelMetadataID, connection);
mAvailableConnectionCount--;
return connection; return connection;
} }
} }
} }
//Preempt an existing connection and connect it to this higher priority source //Preempt an existing lower priority connection and connect when this is a higher priority packet
else //Check for a channel priority override else
{ {
int priority = packet.getMetadata().getAudioPriority(); int priority = audioPacket.getMetadata().getAudioPriority();
if(mLowestPriorityConnection != null &&
priority < mLowestPriorityConnection.getPriority())
{
AudioOutputConnection connection = mLowestPriorityConnection; AudioOutputConnection connection = mLowestPriorityConnection;
if(connection != null && priority < connection.getPriority())
{
mChannelConnectionMap.remove(connection.getChannelMetadataID()); mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.connect(channelMetadataID); connection.connect(channelMetadataID, priority);
mChannelConnectionMap.put(channelMetadataID, connection); mChannelConnectionMap.put(channelMetadataID, connection);
updateLowestPriorityAssignment();
return connection; return connection;
} }
} }
@@ -494,9 +524,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
* Connects this assignment to the indicated source so that audio * Connects this assignment to the indicated source so that audio
* packets from this source can be sent to the audio output * packets from this source can be sent to the audio output
*/ */
public void connect(int source) public void connect(int source, int priority)
{ {
mChannelMetadataID = source; mChannelMetadataID = source;
mPriority = priority;
updateLowestPriorityAssignment();
mAudioOutput.updateTimestamp(); mAudioOutput.updateTimestamp();
} }

View File

@@ -32,7 +32,7 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent> public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
{ {
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" ); private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
public static final int COLUMN_STATE = 0; public static final int COLUMN_STATE = 0;
public static final int COLUMN_DECODER = 1; public static final int COLUMN_DECODER = 1;

View File

@@ -36,14 +36,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.Listener; import sample.Listener;
import sample.real.IOverflowListener; import sample.real.IOverflowListener;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener, public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener,
IOverflowListener
{ {
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class); private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -61,6 +66,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private ChannelType mChannelType; private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener; private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent; private CallEvent mTrafficChannelCallEvent;
private FrequencyChangeListener mFrequencyChangeListener;
private boolean mSquelchLocked = false; private boolean mSquelchLocked = false;
private boolean mSelected = false; private boolean mSelected = false;
@@ -189,6 +195,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mStateMonitor = null; mStateMonitor = null;
} }
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
if(mFrequencyChangeListener == null)
{
mFrequencyChangeListener = new FrequencyChangeListener();
}
return mFrequencyChangeListener;
}
private boolean isStandardChannel() private boolean isStandardChannel()
{ {
return mChannelType == ChannelType.STANDARD; return mChannelType == ChannelType.STANDARD;
@@ -532,6 +549,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
broadcast(mTrafficChannelCallEvent); broadcast(mTrafficChannelCallEvent);
} }
/**
* Listener to receive frequency change events and rebroadcast them to the decoder states
*/
public class FrequencyChangeListener implements Listener<FrequencyChangeEvent>
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
{
long frequency = frequencyChangeEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
}
}
}
/** /**
* DecoderStateEvent receiver wrapper * DecoderStateEvent receiver wrapper
*/ */

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,15 +14,11 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package controller.channel; package controller.channel;
import javax.xml.bind.annotation.XmlAttribute; import controller.config.Configuration;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import module.decode.DecoderFactory; import module.decode.DecoderFactory;
import module.decode.DecoderType; import module.decode.DecoderType;
import module.decode.config.AuxDecodeConfiguration; import module.decode.config.AuxDecodeConfiguration;
@@ -31,6 +27,7 @@ import module.log.EventLogType;
import module.log.config.EventLogConfiguration; import module.log.config.EventLogConfiguration;
import record.RecorderType; import record.RecorderType;
import record.config.RecordConfiguration; import record.config.RecordConfiguration;
import sample.Listener;
import source.SourceType; import source.SourceType;
import source.config.SourceConfigFactory; import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording; import source.config.SourceConfigRecording;
@@ -40,15 +37,24 @@ import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type; import source.tuner.TunerChannel.Type;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeProcessor;
import controller.config.Configuration; import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
@XmlSeeAlso({Configuration.class}) @XmlSeeAlso({Configuration.class})
@XmlRootElement(name = "channel") @XmlRootElement(name = "channel")
public class Channel extends Configuration implements IFrequencyChangeProcessor public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
{ {
// Standard channels are persisted and traffic channels are temporary // Standard channels are persisted and traffic channels are temporary
public enum ChannelType { STANDARD, TRAFFIC }; public enum ChannelType
{
STANDARD, TRAFFIC
}
;
// Static unique channel identifier tracking // Static unique channel identifier tracking
@@ -278,7 +284,7 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
* Note: this method is package private and is intended to be managed * Note: this method is package private and is intended to be managed
* by a co-package central processing manager * by a co-package central processing manager
* *
* @see controller.ChannelProcessingManager * @see controller.channel.ChannelProcessingManager
*/ */
void setEnabled(boolean enabled) void setEnabled(boolean enabled)
{ {
@@ -436,8 +442,7 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
{ {
TunerChannel tunerChannel = getTunerChannel(); TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null && return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
tunerChannel.overlaps( minimum, maximum );
} }
/** /**
@@ -447,7 +452,7 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
* be used to visually show in the spectral display. * be used to visually show in the spectral display.
*/ */
@Override @Override
public void frequencyChanged( FrequencyChangeEvent event ) public void receive(FrequencyChangeEvent event)
{ {
if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE) if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
{ {
@@ -455,9 +460,21 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
} }
} }
/**
* Current channel frequency correction value (when this channel is processing)
* @return
*/
@XmlTransient @XmlTransient
public int getChannelFrequencyCorrection() public int getChannelFrequencyCorrection()
{ {
return mChannelFrequencyCorrection; return mChannelFrequencyCorrection;
} }
/**
* Resets frequency correction value to 0
*/
public void resetFrequencyCorrection()
{
mChannelFrequencyCorrection = 0;
}
} }

View File

@@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener
if(processingChain == null) if(processingChain == null)
{ {
processingChain = new ProcessingChain(channel.getName(), channel.getChannelType()); processingChain = new ProcessingChain(channel.getChannelType());
/* Register global listeners */ /* Register global listeners */
for(Listener<AudioPacket> listener : mAudioPacketListeners) for(Listener<AudioPacket> listener : mAudioPacketListeners)
@@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.addMessageListener(listener); processingChain.addMessageListener(listener);
} }
//Register channel to receive frequency correction events to show in the spectral display (hack!)
processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */ /* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this, List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata()); mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
@@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.removeRecordingModules(); processingChain.removeRecordingModules();
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
processingChain.removeFrequencyChangeListener(channel);
channel.resetFrequencyCorrection();
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP)); mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,23 +14,24 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package dsp.filter.cic; package dsp.filter.cic;
import java.util.ArrayList; import dsp.filter.FilterFactory;
import java.util.List; import dsp.filter.Filters;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import sample.Listener; import sample.Listener;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener; import sample.complex.ComplexSampleListener;
import sample.complex.ComplexToComplexBufferAssembler; import sample.complex.ComplexToComplexBufferAssembler;
import sample.decimator.ComplexDecimator; import sample.decimator.ComplexDecimator;
import dsp.filter.FilterFactory;
import dsp.filter.Filters; import java.util.ArrayList;
import dsp.filter.Window.WindowType; import java.util.List;
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer> public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
{ {
@@ -367,8 +368,11 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
attenuation, attenuation,
windowType), 0.4f); windowType), 0.4f);
mAssembler.setListener( mCleanupFilter ); //Bypassing the CIC cleanup filter for now
mCleanupFilter.setListener( mHalfBandFilter ); mAssembler.setListener(mHalfBandFilter);
// mAssembler.setListener( mCleanupFilter );
// mCleanupFilter.setListener( mHalfBandFilter );
} }
public void dispose() public void dispose()

View File

@@ -1,21 +1,38 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 instrument; package instrument;
import controller.channel.Channel.ChannelType;
import instrument.tap.Tap; import instrument.tap.Tap;
import instrument.tap.TapGroup; import instrument.tap.TapGroup;
import java.util.ArrayList;
import java.util.List;
import module.Module; import module.Module;
import module.ProcessingChain; import module.ProcessingChain;
import source.Source; import source.Source;
import controller.channel.Channel.ChannelType;
import java.util.ArrayList;
import java.util.List;
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
{ {
public InstrumentableProcessingChain() public InstrumentableProcessingChain()
{ {
super( "instrumented", ChannelType.STANDARD ); super(ChannelType.STANDARD);
} }
public void setSource(Source source) throws IllegalStateException public void setSource(Source source) throws IllegalStateException

View File

@@ -108,7 +108,6 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>(); private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private String mName;
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource; protected Source mSource;
@@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener
/** /**
* Creates a processing chain for managing a set of modules * Creates a processing chain for managing a set of modules
* *
* @param name for thread pool naming each thread
* @param channelType * @param channelType
*/ */
public ProcessingChain(String name, ChannelType channelType) public ProcessingChain(ChannelType channelType)
{ {
mName = name;
mChannelState = new ChannelState(channelType); mChannelState = new ChannelState(channelType);
addModule(mChannelState); addModule(mChannelState);
@@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener
} }
mSource = source; mSource = source;
addModule(mSource);
} }
/** /**
@@ -557,25 +555,6 @@ public class ProcessingChain implements IChannelEventListener
+ "sample type - cannot start processing chain"); + "sample type - cannot start processing chain");
} }
/* If this is a tuner source, broadcast the frequency to all
* of the decoder state's and start the samples flowing */
if(mSource instanceof TunerChannelSource)
{
TunerChannelSource tcs = (TunerChannelSource) mSource;
try
{
long frequency = tcs.getFrequency();
mDecoderStateEventBroadcaster.broadcast(
new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, State.IDLE, frequency));
}
catch(SourceException e)
{
mLog.error("Error getting frequency from tuner channel source", e);
}
}
/* Start each of the modules */ /* Start each of the modules */
for(Module module : mModules) for(Module module : mModules)
{ {
@@ -588,8 +567,6 @@ public class ProcessingChain implements IChannelEventListener
mLog.error("Error starting module", e); mLog.error("Error starting module", e);
} }
} }
mSource.start(ThreadPool.SCHEDULED);
} }
else else
{ {
@@ -606,9 +583,16 @@ public class ProcessingChain implements IChannelEventListener
{ {
if(mRunning.compareAndSet(true, false)) if(mRunning.compareAndSet(true, false))
{ {
/* Stop each of the modules */
for(Module module : mModules)
{
module.stop();
}
if(mSource != null) if(mSource != null)
{ {
mSource.stop(); removeModule(mSource);
mSource.setOverflowListener(null); mSource.setOverflowListener(null);
switch(mSource.getSampleType()) switch(mSource.getSampleType())
@@ -629,11 +613,6 @@ public class ProcessingChain implements IChannelEventListener
mSource = null; mSource = null;
} }
/* Stop each of the modules */
for(Module module : mModules)
{
module.stop();
}
} }
} }
@@ -760,6 +739,24 @@ public class ProcessingChain implements IChannelEventListener
mMessageBroadcaster.removeListener(listener); mMessageBroadcaster.removeListener(listener);
} }
/**
* Adds the listener to receive frequency change events from the processing chain
* @param listener to receive events
*/
public void addFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mFrequencyChangeEventBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving frequency change events
* @param listener to remove
*/
public void removeFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mFrequencyChangeEventBroadcaster.removeListener(listener);
}
/** /**
* Adds the listener to receive call events from all modules. * Adds the listener to receive call events from all modules.
*/ */

View File

@@ -9,19 +9,19 @@ import instrument.tap.stream.FloatTap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener; import sample.Listener;
import sample.real.RealBuffer; import sample.real.RealBuffer;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
import sample.real.RealSampleProvider; import sample.real.RealSampleProvider;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeListener; import source.tuner.frequency.IFrequencyChangeListener;
import dsp.gain.DirectGainControl; import dsp.gain.DirectGainControl;
public class C4FMSymbolFilter implements Listener<RealBuffer>, public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
IFrequencyChangeListener,
RealSampleProvider,
Instrumentable
{ {
private static final float TAPS[][] = private static final float TAPS[][] =
{ {
@@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
private RealSampleListener mListener; private RealSampleListener mListener;
private DirectGainControl mGainController = private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
private int mFrequencyAdjustmentRequested = 0; private int mFrequencyAdjustmentRequested = 0;
private int mFrequencyCorrection = 0; private int mFrequencyCorrection = 0;
@@ -258,8 +257,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
correction = -mFrequencyCorrectionMaximum; correction = -mFrequencyCorrectionMaximum;
} }
broadcast( new FrequencyChangeEvent( broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
mFrequencyAdjustmentRequested = 0; mFrequencyAdjustmentRequested = 0;
} }
@@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
} }
} }
/**
* Broadcasts a frequency change event to the registered listener
* @param event
*/
public void broadcast( FrequencyChangeEvent event ) public void broadcast( FrequencyChangeEvent event )
{ {
if( mFrequencyChangeListener != null ) if( mFrequencyChangeListener != null )
@@ -460,6 +462,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
} }
} }
/**
* Sets the listener for frequency change events.
* @param listener
*/
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener ) public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
{ {
mFrequencyChangeListener = listener; mFrequencyChangeListener = listener;

View File

@@ -4,8 +4,10 @@ import alias.AliasList;
import instrument.Instrumentable; import instrument.Instrumentable;
import module.decode.Decoder; import module.decode.Decoder;
import module.decode.DecoderType; import module.decode.DecoderType;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProvider;
public abstract class P25Decoder extends Decoder implements Instrumentable public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
{ {
private P25MessageProcessor mMessageProcessor; private P25MessageProcessor mMessageProcessor;

View File

@@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener<Dibit>
private LSMDemodulator mDemodulator; private LSMDemodulator mDemodulator;
private double mCorrection; private double mCorrection;
public CostasPhaseErrorDetector( FrameSync frameSync, public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
LSMDemodulator demodulator, double correction )
{ {
super( frameSync.getSync() ); super( frameSync.getSync() );

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,6 +14,7 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package module.decode.p25; package module.decode.p25;
@@ -41,8 +42,7 @@ import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType; import dsp.filter.Window.WindowType;
import dsp.filter.fir.real.RealFIRFilter_RB_RB; import dsp.filter.fir.real.RealFIRFilter_RB_RB;
public class P25_C4FMDecoder extends P25Decoder public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener
{ {
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class ); private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );

View File

@@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.gain.ComplexFeedForwardGainControl; import dsp.gain.ComplexFeedForwardGainControl;
import dsp.psk.LSMDemodulator; import dsp.psk.LSMDemodulator;
import dsp.psk.QPSKPolarSlicer; import dsp.psk.QPSKPolarSlicer;
import source.tuner.frequency.FrequencyChangeEvent;
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
{ {
@@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
private List<TapGroup> mAvailableTaps; private List<TapGroup> mAvailableTaps;
private ComplexFIRFilter_CB_CB mBasebandFilter; private ComplexFIRFilter_CB_CB mBasebandFilter;
private ComplexBufferToStreamConverter mStreamConverter = private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
new ComplexBufferToStreamConverter();
private ComplexFeedForwardGainControl mAGC = private ComplexFeedForwardGainControl mAGC =
new ComplexFeedForwardGainControl( 32 ); new ComplexFeedForwardGainControl( 32 );
private LSMDemodulator mLSMDemodulator = new LSMDemodulator(); private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
@@ -108,6 +108,29 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
mMessageFramer = null; mMessageFramer = null;
} }
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
}
@Override
public void removeFrequencyChangeListener()
{
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return new Listener<FrequencyChangeEvent>()
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
//Ignored
}
};
}
@Override @Override
public Listener<ComplexBuffer> getComplexBufferListener() public Listener<ComplexBuffer> getComplexBufferListener()
{ {

View File

@@ -58,6 +58,11 @@ public class Broadcaster<T> implements Listener<T>
public void addListener( Listener<T> listener ) public void addListener( Listener<T> listener )
{ {
if(listener == null)
{
throw new IllegalArgumentException("Listener cannot be null");
}
mListeners.add( listener ); mListeners.add( listener );
} }

View File

@@ -20,11 +20,13 @@ package source;
import module.Module; import module.Module;
import sample.SampleType; import sample.SampleType;
import sample.real.IOverflowListener; import sample.real.IOverflowListener;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProvider;
/** /**
* Abstract class to define the minimum functionality of a sample data provider. * Abstract class to define the minimum functionality of a sample data provider.
*/ */
public abstract class Source extends Module public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider
{ {
protected SampleType mSampleType; protected SampleType mSampleType;
protected IOverflowListener mOverflowListener; protected IOverflowListener mOverflowListener;

View File

@@ -36,11 +36,11 @@ import sample.real.RealBuffer;
import source.RealSource; import source.RealSource;
import source.SourceException; import source.SourceException;
import dsp.gain.AutomaticGainControl; import dsp.gain.AutomaticGainControl;
import source.tuner.frequency.FrequencyChangeEvent;
public class RealMixerSource extends RealSource public class RealMixerSource extends RealSource
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class );
LoggerFactory.getLogger( RealMixerSource.class );
private long mFrequency = 0; private long mFrequency = 0;
private int mBufferSize = 16384; private int mBufferSize = 16384;
@@ -48,13 +48,11 @@ public class RealMixerSource extends RealSource
private BufferReader mBufferReader = new BufferReader(); private BufferReader mBufferReader = new BufferReader();
private TargetDataLine mTargetDataLine; private TargetDataLine mTargetDataLine;
private AudioFormat mAudioFormat; private AudioFormat mAudioFormat;
private String mDisplayName;
private int mBytesPerFrame = 0; private int mBytesPerFrame = 0;
private ISampleAdapter mSampleAdapter; private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl(); private AutomaticGainControl mAGC = new AutomaticGainControl();
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
new CopyOnWriteArrayList<Listener<RealBuffer>>();
/** /**
* Real Mixer Source - constructs a reader on the mixer/sound card target * Real Mixer Source - constructs a reader on the mixer/sound card target
@@ -81,6 +79,25 @@ public class RealMixerSource extends RealSource
mSampleAdapter = sampleAdapter; mSampleAdapter = sampleAdapter;
} }
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override @Override
public void reset() public void reset()
{ {
@@ -133,23 +150,15 @@ public class RealMixerSource extends RealSource
* buffer, otherwise send him a copy of the buffer */ * buffer, otherwise send him a copy of the buffer */
if( it.hasNext() ) if( it.hasNext() )
{ {
// next.receive( mAGC.process( samples.copyOf() ) );
next.receive( samples.copyOf() ); next.receive( samples.copyOf() );
} }
else else
{ {
// next.receive( mAGC.process( samples ) );
next.receive( samples ); next.receive( samples );
} }
} }
} }
@Override
public String toString()
{
return mDisplayName;
}
public int getSampleRate() public int getSampleRate()
{ {
if( mTargetDataLine != null ) if( mTargetDataLine != null )

View File

@@ -34,7 +34,9 @@ import source.ComplexSource;
import source.SourceException; import source.SourceException;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProcessor; import source.tuner.frequency.IFrequencyChangeProcessor;
import source.tuner.frequency.IFrequencyChangeProvider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -65,6 +67,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private ComplexPrimeCICDecimate mDecimationFilter; private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener; private Listener<ComplexBuffer> mListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor; private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture<?> mTaskHandle; private ScheduledFuture<?> mTaskHandle;
private long mTunerFrequency = 0; private long mTunerFrequency = 0;
@@ -75,7 +78,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false; private boolean mExpended = false;
private int mCount;
/** /**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a * Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
@@ -109,7 +111,8 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate()); mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */ /* Fire a sample rate change event to setup the decimation chain */
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate())); frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
mTuner.getTunerController().getSampleRate()));
} }
/** /**
@@ -131,6 +134,10 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
if(mRunning.compareAndSet(false, true)) if(mRunning.compareAndSet(false, true))
{ {
//Broadcast current frequency and sample rate settings so all downstream components are aware
mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency();
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative //Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention //to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS); mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
@@ -184,6 +191,20 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
} }
} }
/**
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
* @param correction current frequency correction value.
*/
private void setFrequencyCorrection(int correction)
{
mChannelFrequencyCorrection = correction;
updateMixerFrequencyOffset();
mDownstreamFrequencyEventProcessor.broadcast(
new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
public Tuner getTuner() public Tuner getTuner()
{ {
return mTuner; return mTuner;
@@ -242,21 +263,25 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
case NOTIFICATION_FREQUENCY_CHANGE: case NOTIFICATION_FREQUENCY_CHANGE:
mTunerFrequency = event.getValue().longValue(); mTunerFrequency = event.getValue().longValue();
updateMixerFrequencyOffset(); updateMixerFrequencyOffset();
break;
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
mChannelFrequencyCorrection = event.getValue().intValue();
updateMixerFrequencyOffset(); //Reset frequency correction so that downstream components can recalculate the value
setFrequencyCorrection(0);
if(mFrequencyChangeProcessor != null)
{
mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
break; break;
case NOTIFICATION_SAMPLE_RATE_CHANGE: case NOTIFICATION_SAMPLE_RATE_CHANGE:
int sampleRate = event.getValue().intValue(); int sampleRate = event.getValue().intValue();
setSampleRate(sampleRate);
break;
default:
break;
}
}
/**
* Updates the sample rate to the requested value and notifies any downstream components of the change
* @param sampleRate to set
*/
private void setSampleRate(int sampleRate)
{
if(mTunerSampleRate != sampleRate) if(mTunerSampleRate != sampleRate)
{ {
mMixer.setSampleRate(sampleRate); mMixer.setSampleRate(sampleRate);
@@ -269,10 +294,8 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
mDecimationFilter.setListener(mListener); mDecimationFilter.setListener(mListener);
mTunerSampleRate = sampleRate; mTunerSampleRate = sampleRate;
}
break; mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
default:
break;
} }
} }
@@ -282,10 +305,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
*/ */
private void updateMixerFrequencyOffset() private void updateMixerFrequencyOffset()
{ {
long offset = mTunerFrequency - long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
mTunerChannel.getFrequency() -
mChannelFrequencyCorrection;
mMixer.setFrequency(offset); mMixer.setFrequency(offset);
} }
@@ -299,6 +319,140 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
return mTunerChannel.getFrequency(); return mTunerChannel.getFrequency();
} }
/**
* Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream
* listeners.
*
* @param listener to receive downstream events
*/
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener);
}
/**
* Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency
* change events.
*/
@Override
public void removeFrequencyChangeListener()
{
mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener();
}
/**
* Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream
* listeners to change frequency values.
* @return listener
*/
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
}
/**
* Managers frequency change requests and notifications from/to any downstream component. Downstream
* components are those that receive samples from this tuner channel source. These downstream components will be
* notified of any frequency or sample rate change events and will also be able to request frequency correction
* updates.
*/
public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider,
Listener<FrequencyChangeEvent>
{
//Listener to receive downstream events
private Listener<FrequencyChangeEvent> mListener;
/**
* Broadcasts the frequency change event to the downstream frequency change listener
* @param event to broadcast
*/
public void broadcast(FrequencyChangeEvent event)
{
if(mListener != null)
{
mListener.receive(event);
}
}
/**
* Broadcasts the current frequency of this tuner channel source to the downstream listener
*/
public void broadcastCurrentFrequency()
{
try
{
long frequency = getFrequency();
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency));
}
catch(SourceException se)
{
mLog.error("Error obtaining frequency from tuner to broadcast downstream");
}
}
/**
* Broadcasts the current decimated sample rate of this tuner channel source
*/
public void broadcastCurrentSampleRate()
{
try
{
//Note: downstream sample rate is currently a fixed value -- it will change in the future
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate()));
}
catch(SourceException se)
{
mLog.error("Error obtaining sample rate from tuner to broadcast downstream");
}
}
/**
* Sets the downstream listener to receive frequency change events from this tuner channel source
* @param listener to receive events
*/
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mListener = listener;
}
/**
* Removes the downstream listener from receiving frequency change events.
*/
@Override
public void removeFrequencyChangeListener()
{
mListener = null;
}
/**
* Listener for receiving frequency change events from downstream components
*/
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return this;
}
/**
* Processes frequency change events from downstream components.
* @param event to process
*/
@Override
public void receive(FrequencyChangeEvent event)
{
switch(event.getEvent())
{
//Frequency correction requests are the only change requests supported from downstream components
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setFrequencyCorrection(event.getValue().intValue());
break;
}
}
}
/** /**
* Decimates an inbound buffer of I/Q samples from the source down to the * Decimates an inbound buffer of I/Q samples from the source down to the
* standard 48000 channel sample rate * standard 48000 channel sample rate

View File

@@ -1,9 +1,30 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 source.tuner.frequency; package source.tuner.frequency;
import sample.Listener; import sample.Listener;
/**
* Provider of Frequency Change Events
*/
public interface IFrequencyChangeProvider public interface IFrequencyChangeProvider
{ {
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener ); void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
public void removeFrequencyChangeListener(); void removeFrequencyChangeListener();
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,6 +14,7 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package source.wave; package source.wave;
@@ -36,6 +37,7 @@ import sample.complex.ComplexBuffer;
import source.ComplexSource; import source.ComplexSource;
import source.IControllableFileSource; import source.IControllableFileSource;
import source.IFrameLocationListener; import source.IFrameLocationListener;
import source.tuner.frequency.FrequencyChangeEvent;
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
{ {
@@ -55,6 +57,25 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
mFile = file; mFile = file;
} }
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override @Override
public void reset() public void reset()
{ {

View File

@@ -36,6 +36,7 @@ import sample.real.RealBuffer;
import source.IControllableFileSource; import source.IControllableFileSource;
import source.IFrameLocationListener; import source.IFrameLocationListener;
import source.RealSource; import source.RealSource;
import source.tuner.frequency.FrequencyChangeEvent;
public class RealWaveSource extends RealSource public class RealWaveSource extends RealSource
implements IControllableFileSource, AutoCloseable implements IControllableFileSource, AutoCloseable
@@ -56,6 +57,25 @@ public class RealWaveSource extends RealSource
mFile = file; mFile = file;
} }
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override @Override
public void reset() public void reset()

View File

@@ -50,6 +50,7 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class); private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class);
private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" );
private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING, private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); RenderingHints.VALUE_ANTIALIAS_ON);
@@ -187,7 +188,6 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
* 6 64x Zoom * 6 64x Zoom
* *
* @param zoom level, 0 - 6. * @param zoom level, 0 - 6.
* @param offset into the FFT bins for start zoom window
*/ */
public void setZoom(int zoom) public void setZoom(int zoom)
{ {
@@ -412,20 +412,41 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
} }
/** /**
* Draws a vertical line at the xaxis * Draws the Automatic Frequency Control (AFC) channel center offset
*/ */
private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis) private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis, double bandwidth,
int correction, long frequency)
{ {
double height = getSize().getHeight() - mSpectrumInset; double height = getSize().getHeight() - mSpectrumInset;
double lineHeight = height * 0.75d; double verticalAxisTop = height * 0.88d;
double verticalAxisBottom = height * 0.98d;
double halfBandwidth = bandwidth / 2.0;
double errorEdgeStart = errorAxis - halfBandwidth;
double errorEdgeStop = errorAxis + halfBandwidth;
graphics.setColor(Color.YELLOW); graphics.setColor(Color.YELLOW);
//Vertical frequency error line //Horizontal line connecting frequency and error line
graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d)); graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisBottom, errorEdgeStop, verticalAxisBottom));
//Horizontal line connecting frequency and error lines //Vertical band edge lines
graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight)); graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisTop, errorEdgeStart, verticalAxisBottom));
graphics.draw(new Line2D.Double(errorEdgeStop, verticalAxisTop, errorEdgeStop, verticalAxisBottom));
double ppm = (double)correction / ((double)frequency / 1E6d);
String label = "PPM " + PPM_FORMATTER.format(ppm) ;
FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
Rectangle2D rect = fontMetrics.getStringBounds(label, graphics);
//Only render the correction value label if the spacing is large enough
if(rect.getWidth() <= bandwidth && rect.getHeight() * 5 <= height)
{
graphics.drawString(label, (float)(errorEdgeStart + 1.0), (float)(verticalAxisBottom - 2.0));
}
} }
/** /**
@@ -564,7 +585,8 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
{ {
long error = frequency + correction; long error = frequency + correction;
drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error)); drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error), width, correction,
tunerChannel.getFrequency());
} }
} }
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,56 +14,43 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package spectrum; package spectrum;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.util.Arrays;
import javax.swing.JPanel;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
import dsp.filter.smoothing.GaussianSmoothingFilter; import dsp.filter.smoothing.GaussianSmoothingFilter;
import dsp.filter.smoothing.NoSmoothingFilter; import dsp.filter.smoothing.NoSmoothingFilter;
import dsp.filter.smoothing.RectangularSmoothingFilter; import dsp.filter.smoothing.RectangularSmoothingFilter;
import dsp.filter.smoothing.SmoothingFilter; import dsp.filter.smoothing.SmoothingFilter;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType; import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
import dsp.filter.smoothing.TriangularSmoothingFilter; import dsp.filter.smoothing.TriangularSmoothingFilter;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
public class SpectrumPanel extends JPanel import javax.swing.*;
implements DFTResultsListener, import java.awt.*;
SettingChangeListener, import java.awt.geom.GeneralPath;
SpectralDisplayAdjuster import java.awt.geom.Line2D;
import java.util.Arrays;
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class);
LoggerFactory.getLogger( SpectrumPanel.class );
private static final RenderingHints RENDERING_HINTS = private static final RenderingHints RENDERING_HINTS =
new RenderingHints( RenderingHints.KEY_ANTIALIASING, new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints.VALUE_ANTIALIAS_ON );
static static
{ {
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
RenderingHints.VALUE_RENDER_QUALITY );
} }
private Color mColorSpectrumBackground; private Color mColorSpectrumBackground;
@@ -87,7 +74,8 @@ public class SpectrumPanel extends JPanel
private float mDBScale; private float mDBScale;
private int mZoom = 0; private int mZoom = 0;
private int mZoomWindowOffset = 0;; private int mZoomWindowOffset = 0;
;
private SettingsManager mSettingsManager; private SettingsManager mSettingsManager;
@@ -278,12 +266,13 @@ public class SpectrumPanel extends JPanel
* 3 8x Zoom * 3 8x Zoom
* 4 16x Zoom * 4 16x Zoom
* 5 32x Zoom * 5 32x Zoom
* 6 64x Zoom
* *
* @param zoom level, 0 - 5. * @param zoom level, 0 - 6.
*/ */
public void setZoom(int zoom) public void setZoom(int zoom)
{ {
Validate.isTrue( 0 <= zoom && zoom <= 5 ); Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom);
mZoom = zoom; mZoom = zoom;
} }
@@ -427,7 +416,8 @@ public class SpectrumPanel extends JPanel
} }
@Override @Override
public void settingDeleted( Setting setting ) { /* not implemented */ } public void settingDeleted(Setting setting)
{ /* not implemented */ }
/** /**
* Returns the current inter-bin smoothing setting * Returns the current inter-bin smoothing setting