From 1aa85a86ca441dc765a9eb3c18f08f825ea17497 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 11 Mar 2017 10:16:08 -0500 Subject: [PATCH] Resolves issue with channel frequency correction events not being broadcast within the processing chain and rendering in the spectral display. --- src/channel/state/ChannelState.java | 36 +- src/controller/channel/Channel.java | 807 ++++++++--------- .../channel/ChannelProcessingManager.java | 8 +- .../InstrumentableProcessingChain.java | 117 +-- src/module/ProcessingChain.java | 61 +- src/module/decode/p25/C4FMSymbolFilter.java | 28 +- src/module/decode/p25/P25Decoder.java | 4 +- src/module/decode/p25/P25MessageFramer.java | 3 +- src/module/decode/p25/P25_C4FMDecoder.java | 34 +- src/module/decode/p25/P25_LSMDecoder.java | 24 +- src/source/Source.java | 4 +- src/source/mixer/RealMixerSource.java | 39 +- src/source/tuner/TunerChannelSource.java | 214 ++++- .../frequency/IFrequencyChangeProvider.java | 25 +- src/source/wave/ComplexWaveSource.java | 55 +- src/source/wave/RealWaveSource.java | 24 +- src/spectrum/OverlayPanel.java | 40 +- src/spectrum/SpectrumPanel.java | 816 +++++++++--------- 18 files changed, 1336 insertions(+), 1003 deletions(-) diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java index 768c1062..d9407c0f 100644 --- a/src/channel/state/ChannelState.java +++ b/src/channel/state/ChannelState.java @@ -36,14 +36,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; import sample.real.IOverflowListener; +import source.tuner.frequency.FrequencyChangeEvent; +import source.tuner.frequency.IFrequencyChangeListener; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE; + 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); @@ -61,6 +66,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder private ChannelType mChannelType; private TrafficChannelManager mTrafficChannelEndListener; private CallEvent mTrafficChannelCallEvent; + private FrequencyChangeListener mFrequencyChangeListener; private boolean mSquelchLocked = false; private boolean mSelected = false; @@ -189,6 +195,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder mStateMonitor = null; } + @Override + public Listener getFrequencyChangeListener() + { + if(mFrequencyChangeListener == null) + { + mFrequencyChangeListener = new FrequencyChangeListener(); + } + + return mFrequencyChangeListener; + } + private boolean isStandardChannel() { return mChannelType == ChannelType.STANDARD; @@ -532,6 +549,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder broadcast(mTrafficChannelCallEvent); } + /** + * Listener to receive frequency change events and rebroadcast them to the decoder states + */ + public class FrequencyChangeListener implements Listener + { + @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 */ diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 81ced518..79be174b 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -1,28 +1,24 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 + * 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 + * ******************************************************************************/ package controller.channel; -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; - +import controller.config.Configuration; import module.decode.DecoderFactory; import module.decode.DecoderType; import module.decode.config.AuxDecodeConfiguration; @@ -31,6 +27,7 @@ import module.log.EventLogType; import module.log.config.EventLogConfiguration; import record.RecorderType; import record.config.RecordConfiguration; +import sample.Listener; import source.SourceType; import source.config.SourceConfigFactory; import source.config.SourceConfigRecording; @@ -40,424 +37,444 @@ import source.tuner.TunerChannel; import source.tuner.TunerChannel.Type; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent.Event; -import source.tuner.frequency.IFrequencyChangeProcessor; -import controller.config.Configuration; -@XmlSeeAlso( { Configuration.class } ) -@XmlRootElement( name = "channel" ) -public class Channel extends Configuration implements IFrequencyChangeProcessor +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}) +@XmlRootElement(name = "channel") +public class Channel extends Configuration implements Listener { - // Standard channels are persisted and traffic channels are temporary - public enum ChannelType { STANDARD, TRAFFIC }; - + // Standard channels are persisted and traffic channels are temporary + public enum ChannelType + { + STANDARD, TRAFFIC + } - // Static unique channel identifier tracking - private static int UNIQUE_ID = 0; + ; - private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration(); - private AuxDecodeConfiguration mAuxDecodeConfiguration = - new AuxDecodeConfiguration(); - private SourceConfiguration mSourceConfiguration = - SourceConfigFactory.getDefaultSourceConfiguration(); - private EventLogConfiguration mEventLogConfiguration = - new EventLogConfiguration(); - private RecordConfiguration mRecordConfiguration = new RecordConfiguration(); - private String mAliasListName; - private String mSystem = "System"; + // Static unique channel identifier tracking + private static int UNIQUE_ID = 0; + + private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration(); + private AuxDecodeConfiguration mAuxDecodeConfiguration = + new AuxDecodeConfiguration(); + private SourceConfiguration mSourceConfiguration = + SourceConfigFactory.getDefaultSourceConfiguration(); + private EventLogConfiguration mEventLogConfiguration = + new EventLogConfiguration(); + private RecordConfiguration mRecordConfiguration = new RecordConfiguration(); + + private String mAliasListName; + private String mSystem = "System"; private String mSite = "Site"; private String mName = "Channel"; - - private boolean mEnabled; - private boolean mSelected; - private TunerChannel mTunerChannel = null; - - private ChannelType mChannelType = ChannelType.STANDARD; - private int mChannelID; - private int mChannelFrequencyCorrection = 0; - - /** - * Channel represents a complete set of configurations needed to setup and - * manage a processing chain and/or manage as a set of business objects that - * can be displayed in graphical components. - * - * @param channelName - * @param channelType - */ - public Channel( String channelName, ChannelType channelType ) - { - this( channelName ); - mChannelType = channelType; - } + private boolean mEnabled; + private boolean mSelected; + private TunerChannel mTunerChannel = null; - /** - * Constructs a new standard channel with the specified channel name - */ - public Channel( String channelName ) - { - this(); - mName = channelName; - } + private ChannelType mChannelType = ChannelType.STANDARD; - /** - * Constructs a new standard channel with a default name of "Channel" - */ - public Channel() - { - mChannelID = UNIQUE_ID++; - } + private int mChannelID; + private int mChannelFrequencyCorrection = 0; - /** - * Creates a (new) deep copy of this channel - */ - public Channel copyOf() - { - Channel channel = new Channel( mName ); - channel.setSystem( mSystem ); - channel.setSite( mSite ); - channel.setAliasListName( mAliasListName ); + /** + * Channel represents a complete set of configurations needed to setup and + * manage a processing chain and/or manage as a set of business objects that + * can be displayed in graphical components. + * + * @param channelName + * @param channelType + */ + public Channel(String channelName, ChannelType channelType) + { + this(channelName); + mChannelType = channelType; + } - AuxDecodeConfiguration aux = new AuxDecodeConfiguration(); - - for( DecoderType auxType: aux.getAuxDecoders() ) - { - aux.addAuxDecoder( auxType ); - } - - channel.setAuxDecodeConfiguration( aux ); - - channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) ); - - EventLogConfiguration log = new EventLogConfiguration(); - - for( EventLogType logType: mEventLogConfiguration.getLoggers() ) - { - log.addLogger( logType ); - } - - channel.setEventLogConfiguration( log ); + /** + * Constructs a new standard channel with the specified channel name + */ + public Channel(String channelName) + { + this(); + mName = channelName; + } - RecordConfiguration record = new RecordConfiguration(); - - for( RecorderType recordType: mRecordConfiguration.getRecorders() ) - { - record.addRecorder( recordType ); - } - - channel.setRecordConfiguration( record ); + /** + * Constructs a new standard channel with a default name of "Channel" + */ + public Channel() + { + mChannelID = UNIQUE_ID++; + } + + /** + * Creates a (new) deep copy of this channel + */ + public Channel copyOf() + { + Channel channel = new Channel(mName); + channel.setSystem(mSystem); + channel.setSite(mSite); + channel.setAliasListName(mAliasListName); + + AuxDecodeConfiguration aux = new AuxDecodeConfiguration(); + + for(DecoderType auxType : aux.getAuxDecoders()) + { + aux.addAuxDecoder(auxType); + } + + channel.setAuxDecodeConfiguration(aux); + + channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration)); + + EventLogConfiguration log = new EventLogConfiguration(); + + for(EventLogType logType : mEventLogConfiguration.getLoggers()) + { + log.addLogger(logType); + } + + channel.setEventLogConfiguration(log); + + RecordConfiguration record = new RecordConfiguration(); + + for(RecorderType recordType : mRecordConfiguration.getRecorders()) + { + record.addRecorder(recordType); + } + + channel.setRecordConfiguration(record); + + channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration)); + + return channel; + } + + /** + * Unique identifier for this channel. Value is transient (not persisted). + */ + @XmlTransient + public int getChannelID() + { + return mChannelID; + } + + /** + * Indicates if this channel has been selected for prioritized audio output + * and display of processing chain products + */ + @XmlTransient + public boolean isSelected() + { + return mSelected; + } + + /** + * Sets selection status of the channel. + * + * NOTE: this method is package private so that only the ChannelSelectionManager + * can toggle the selection state. Use the channel model to broadcast a + * request select/deselect channel event. + * + * @param selected + */ + void setSelected(boolean selected) + { + mSelected = selected; + } + + @XmlTransient + public ChannelType getChannelType() + { + return mChannelType; + } - channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) ); - - return channel; - } - - /** - * Unique identifier for this channel. Value is transient (not persisted). - */ - @XmlTransient - public int getChannelID() - { - return mChannelID; - } - - /** - * Indicates if this channel has been selected for prioritized audio output - * and display of processing chain products - */ - @XmlTransient - public boolean isSelected() - { - return mSelected; - } - - /** - * Sets selection status of the channel. - * - * NOTE: this method is package private so that only the ChannelSelectionManager - * can toggle the selection state. Use the channel model to broadcast a - * request select/deselect channel event. - * - * @param selected - */ - void setSelected( boolean selected ) - { - mSelected = selected; - } - - @XmlTransient - public ChannelType getChannelType() - { - return mChannelType; - } - /** * Returns the owning system for this channel. */ - @XmlAttribute( name = "system" ) - public String getSystem() - { - return mSystem; - } - - public void setSystem( String system ) - { - mSystem = system; - } + @XmlAttribute(name = "system") + public String getSystem() + { + return mSystem; + } - public boolean hasSystem() - { - return mSystem != null; - } - - /** - * Returns the owning site for this channel. - */ - @XmlAttribute( name = "site" ) - public String getSite() - { - return mSite; - } - - public void setSite( String site ) - { - mSite = site; - } - - public boolean hasSite() - { - return mSite != null; - } - - /** - * Returns the name of this channel. - */ - @XmlAttribute( name = "name" ) - public String getName() - { - return mName; - } - - /** - * Sets the name of this channel. - */ - public void setName( String name ) - { - mName = name; - } + public void setSystem(String system) + { + mSystem = system; + } + + public boolean hasSystem() + { + return mSystem != null; + } + + /** + * Returns the owning site for this channel. + */ + @XmlAttribute(name = "site") + public String getSite() + { + return mSite; + } + + public void setSite(String site) + { + mSite = site; + } + + public boolean hasSite() + { + return mSite != null; + } + + /** + * Returns the name of this channel. + */ + @XmlAttribute(name = "name") + public String getName() + { + return mName; + } + + /** + * Sets the name of this channel. + */ + public void setName(String name) + { + mName = name; + } /** * Default display string for this channel: SYSTEM_SITE_NAME */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( hasSystem() ? getSystem() : "SYSTEM" ); - sb.append( "_" ); - sb.append( hasSite() ? getSite() : "SITE" ); - sb.append( "_" ); - sb.append( getName() ); - - return sb.toString(); - } + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(hasSystem() ? getSystem() : "SYSTEM"); + sb.append("_"); + sb.append(hasSite() ? getSite() : "SITE"); + sb.append("_"); + sb.append(getName()); - /** - * Indicates if this channel is enabled for processing. - */ - @XmlAttribute( name = "enabled" ) - public boolean getEnabled() - { - return mEnabled; - } + return sb.toString(); + } - /** - * Indicates if this channel will automatically start processing on - * application startup or if this channel is currently processing after - * application startup. - * - * Note: this method is package private and is intended to be managed - * by a co-package central processing manager - * - * @see controller.ChannelProcessingManager - */ - void setEnabled( boolean enabled ) - { - mEnabled = enabled; - } + /** + * Indicates if this channel is enabled for processing. + */ + @XmlAttribute(name = "enabled") + public boolean getEnabled() + { + return mEnabled; + } - /** - * Returns the alias list that is used by this channel for looking up alias - * values for the various identifiers produced by the decoder(s). - */ - @XmlElement( name = "alias_list_name" ) - public String getAliasListName() - { - return mAliasListName; - } + /** + * Indicates if this channel will automatically start processing on + * application startup or if this channel is currently processing after + * application startup. + * + * Note: this method is package private and is intended to be managed + * by a co-package central processing manager + * + * @see controller.channel.ChannelProcessingManager + */ + void setEnabled(boolean enabled) + { + mEnabled = enabled; + } - /** - * Sets the alias list to be used for looking up the alias values for the - * various identifiers produced by the decoder - */ - public void setAliasListName( String name ) - { - mAliasListName = name; - } + /** + * Returns the alias list that is used by this channel for looking up alias + * values for the various identifiers produced by the decoder(s). + */ + @XmlElement(name = "alias_list_name") + public String getAliasListName() + { + return mAliasListName; + } - /** - * Gets the primary decoder configuration used by this channel - */ - @XmlElement( name = "decode_configuration" ) - public DecodeConfiguration getDecodeConfiguration() - { - return mDecodeConfiguration; - } + /** + * Sets the alias list to be used for looking up the alias values for the + * various identifiers produced by the decoder + */ + public void setAliasListName(String name) + { + mAliasListName = name; + } - /** - * Sets the primary decoder configuration used by this channel - */ - public void setDecodeConfiguration( DecodeConfiguration config ) - { - mDecodeConfiguration = config; - } + /** + * Gets the primary decoder configuration used by this channel + */ + @XmlElement(name = "decode_configuration") + public DecodeConfiguration getDecodeConfiguration() + { + return mDecodeConfiguration; + } - /** - * Gets the aux decoder configuration used by this channel. Aux decoders - * operate on demodulated audio - */ - @XmlElement( name = "aux_decode_configuration" ) - public AuxDecodeConfiguration getAuxDecodeConfiguration() - { - return mAuxDecodeConfiguration; - } + /** + * Sets the primary decoder configuration used by this channel + */ + public void setDecodeConfiguration(DecodeConfiguration config) + { + mDecodeConfiguration = config; + } - /** - * Sets the decoder configuration used by this channel - */ - public void setAuxDecodeConfiguration( AuxDecodeConfiguration config ) - { - mAuxDecodeConfiguration = config; - } + /** + * Gets the aux decoder configuration used by this channel. Aux decoders + * operate on demodulated audio + */ + @XmlElement(name = "aux_decode_configuration") + public AuxDecodeConfiguration getAuxDecodeConfiguration() + { + return mAuxDecodeConfiguration; + } - /** - * Returns the source configuration for this channel. A channel source - * identifies where the processing chain will get source sample data from. - */ - @XmlElement( name = "source_configuration" ) - public SourceConfiguration getSourceConfiguration() - { - return mSourceConfiguration; - } + /** + * Sets the decoder configuration used by this channel + */ + public void setAuxDecodeConfiguration(AuxDecodeConfiguration config) + { + mAuxDecodeConfiguration = config; + } - /** - * Sets the source configuration for this channel. - */ - public void setSourceConfiguration( SourceConfiguration config ) - { - mSourceConfiguration = config; + /** + * Returns the source configuration for this channel. A channel source + * identifies where the processing chain will get source sample data from. + */ + @XmlElement(name = "source_configuration") + public SourceConfiguration getSourceConfiguration() + { + return mSourceConfiguration; + } - //Clear the tune channel object so that it can be recreated if the - //source configuration changes - mTunerChannel = null; - } + /** + * Sets the source configuration for this channel. + */ + public void setSourceConfiguration(SourceConfiguration config) + { + mSourceConfiguration = config; - /** - * Returns the event logger configuration for this channel. - */ - @XmlElement( name = "event_log_configuration" ) - public EventLogConfiguration getEventLogConfiguration() - { - return mEventLogConfiguration; - } + //Clear the tune channel object so that it can be recreated if the + //source configuration changes + mTunerChannel = null; + } - /** - * Sets the event logger configuration for this channel. - */ - public void setEventLogConfiguration( EventLogConfiguration config ) - { - mEventLogConfiguration = config; - } + /** + * Returns the event logger configuration for this channel. + */ + @XmlElement(name = "event_log_configuration") + public EventLogConfiguration getEventLogConfiguration() + { + return mEventLogConfiguration; + } - /** - * Returns the recorder configuration for this channel. - */ - @XmlElement( name = "record_configuration" ) - public RecordConfiguration getRecordConfiguration() - { - return mRecordConfiguration; - } + /** + * Sets the event logger configuration for this channel. + */ + public void setEventLogConfiguration(EventLogConfiguration config) + { + mEventLogConfiguration = config; + } - /** - * Sets the recorder configuration for this channel. - */ - public void setRecordConfiguration( RecordConfiguration config ) - { - mRecordConfiguration = config; - } + /** + * Returns the recorder configuration for this channel. + */ + @XmlElement(name = "record_configuration") + public RecordConfiguration getRecordConfiguration() + { + return mRecordConfiguration; + } - /** - * Convenience method to construct a tuner channel object representing a - * tuner or recording source frequency and bandwidth that can be used by - * application components for graphically representing this channel. - * - * If the source configuration is not a tuner or recording, this method - * returns null. - */ - @XmlTransient + /** + * Sets the recorder configuration for this channel. + */ + public void setRecordConfiguration(RecordConfiguration config) + { + mRecordConfiguration = config; + } + + /** + * Convenience method to construct a tuner channel object representing a + * tuner or recording source frequency and bandwidth that can be used by + * application components for graphically representing this channel. + * + * If the source configuration is not a tuner or recording, this method + * returns null. + */ + @XmlTransient public TunerChannel getTunerChannel() { - if( mTunerChannel == null ) - { - if( mSourceConfiguration.getSourceType() == SourceType.TUNER ) - { - SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration; + if(mTunerChannel == null) + { + if(mSourceConfiguration.getSourceType() == SourceType.TUNER) + { + SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration; - mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(), - mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); - } - else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING ) - { - SourceConfigRecording config = - (SourceConfigRecording)mSourceConfiguration; + mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(), + mDecodeConfiguration.getDecoderType().getChannelBandwidth()); + } + else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING) + { + SourceConfigRecording config = + (SourceConfigRecording)mSourceConfiguration; + + mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(), + mDecodeConfiguration.getDecoderType().getChannelBandwidth()); + } + } - mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(), - mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); - } - } - return mTunerChannel; } - /** - * Convenience method to indicate if any part of this channel is contained - * within the minimum and maximum frequency values. - */ - public boolean isWithin( long minimum, long maximum ) - { - TunerChannel tunerChannel = getTunerChannel(); - - return tunerChannel != null && - tunerChannel.overlaps( minimum, maximum ); - } - - /** - * Convenience method to make the current channel frequency correction value - * available for use outside of a constructed processing chain while maintaining - * linkage to the source channel. This hack allows the correction value to - * be used to visually show in the spectral display. - */ - @Override - public void frequencyChanged( FrequencyChangeEvent event ) - { - if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE ) - { - mChannelFrequencyCorrection = event.getValue().intValue(); - } - } + /** + * Convenience method to indicate if any part of this channel is contained + * within the minimum and maximum frequency values. + */ + public boolean isWithin(long minimum, long maximum) + { + TunerChannel tunerChannel = getTunerChannel(); - @XmlTransient - public int getChannelFrequencyCorrection() - { - return mChannelFrequencyCorrection; - } + return tunerChannel != null && tunerChannel.overlaps(minimum, maximum); + } + + /** + * Convenience method to make the current channel frequency correction value + * available for use outside of a constructed processing chain while maintaining + * linkage to the source channel. This hack allows the correction value to + * be used to visually show in the spectral display. + */ + @Override + public void receive(FrequencyChangeEvent event) + { + if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE) + { + mChannelFrequencyCorrection = event.getValue().intValue(); + } + } + + /** + * Current channel frequency correction value (when this channel is processing) + * @return + */ + @XmlTransient + public int getChannelFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Resets frequency correction value to 0 + */ + public void resetFrequencyCorrection() + { + mChannelFrequencyCorrection = 0; + } } diff --git a/src/controller/channel/ChannelProcessingManager.java b/src/controller/channel/ChannelProcessingManager.java index 93bf1f0c..ad65bd3c 100644 --- a/src/controller/channel/ChannelProcessingManager.java +++ b/src/controller/channel/ChannelProcessingManager.java @@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener if(processingChain == null) { - processingChain = new ProcessingChain(channel.getName(), channel.getChannelType()); + processingChain = new ProcessingChain(channel.getChannelType()); /* Register global listeners */ for(Listener listener : mAudioPacketListeners) @@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener processingChain.addMessageListener(listener); } + //Register channel to receive frequency correction events to show in the spectral display (hack!) + processingChain.addFrequencyChangeListener(channel); + /* Processing Modules */ List modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this, mAliasModel, channel, processingChain.getChannelState().getMutableMetadata()); @@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener 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)); diff --git a/src/instrument/InstrumentableProcessingChain.java b/src/instrument/InstrumentableProcessingChain.java index 882eb891..0ca61c90 100644 --- a/src/instrument/InstrumentableProcessingChain.java +++ b/src/instrument/InstrumentableProcessingChain.java @@ -1,65 +1,82 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ package instrument; +import controller.channel.Channel.ChannelType; import instrument.tap.Tap; import instrument.tap.TapGroup; +import module.Module; +import module.ProcessingChain; +import source.Source; import java.util.ArrayList; import java.util.List; -import module.Module; -import module.ProcessingChain; -import source.Source; -import controller.channel.Channel.ChannelType; - public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable { - public InstrumentableProcessingChain() - { - super( "instrumented", ChannelType.STANDARD ); - } + public InstrumentableProcessingChain() + { + super(ChannelType.STANDARD); + } - public void setSource( Source source ) throws IllegalStateException - { - mSource = source; - } + public void setSource(Source source) throws IllegalStateException + { + mSource = source; + } - @Override - public List getTapGroups() - { - List groups = new ArrayList<>(); - - for( Module module: getModules() ) - { - if( module instanceof Instrumentable ) - { - groups.addAll( ((Instrumentable)module).getTapGroups() ); - } - } - - return groups; - } + @Override + public List getTapGroups() + { + List groups = new ArrayList<>(); - @Override - public void registerTap( Tap tap ) - { - for( Module module: getModules() ) - { - if( module instanceof Instrumentable ) - { - ((Instrumentable)module).registerTap( tap ); - } - } - } + for(Module module : getModules()) + { + if(module instanceof Instrumentable) + { + groups.addAll(((Instrumentable)module).getTapGroups()); + } + } - @Override - public void unregisterTap( Tap tap ) - { - for( Module module: getModules() ) - { - if( module instanceof Instrumentable ) - { - ((Instrumentable)module).unregisterTap( tap ); - } - } - } + return groups; + } + + @Override + public void registerTap(Tap tap) + { + for(Module module : getModules()) + { + if(module instanceof Instrumentable) + { + ((Instrumentable)module).registerTap(tap); + } + } + } + + @Override + public void unregisterTap(Tap tap) + { + for(Module module : getModules()) + { + if(module instanceof Instrumentable) + { + ((Instrumentable)module).unregisterTap(tap); + } + } + } } diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index fdf60e86..a018e360 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -108,7 +108,6 @@ public class ProcessingChain implements IChannelEventListener private Broadcaster mUnFilteredRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>(); - private String mName; private AtomicBoolean mRunning = new AtomicBoolean(); protected Source mSource; @@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener /** * Creates a processing chain for managing a set of modules * - * @param name for thread pool naming each thread * @param channelType */ - public ProcessingChain(String name, ChannelType channelType) + public ProcessingChain(ChannelType channelType) { - mName = name; - mChannelState = new ChannelState(channelType); addModule(mChannelState); @@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener } mSource = source; + + addModule(mSource); } /** @@ -557,25 +555,6 @@ public class ProcessingChain implements IChannelEventListener + "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 */ for(Module module : mModules) { @@ -588,8 +567,6 @@ public class ProcessingChain implements IChannelEventListener mLog.error("Error starting module", e); } } - - mSource.start(ThreadPool.SCHEDULED); } else { @@ -606,9 +583,16 @@ public class ProcessingChain implements IChannelEventListener { if(mRunning.compareAndSet(true, false)) { + /* Stop each of the modules */ + for(Module module : mModules) + { + module.stop(); + } + if(mSource != null) { - mSource.stop(); + removeModule(mSource); + mSource.setOverflowListener(null); switch(mSource.getSampleType()) @@ -629,11 +613,6 @@ public class ProcessingChain implements IChannelEventListener 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); } + /** + * Adds the listener to receive frequency change events from the processing chain + * @param listener to receive events + */ + public void addFrequencyChangeListener(Listener listener) + { + mFrequencyChangeEventBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving frequency change events + * @param listener to remove + */ + public void removeFrequencyChangeListener(Listener listener) + { + mFrequencyChangeEventBroadcaster.removeListener(listener); + } + /** * Adds the listener to receive call events from all modules. */ diff --git a/src/module/decode/p25/C4FMSymbolFilter.java b/src/module/decode/p25/C4FMSymbolFilter.java index 9b91c6e0..51e7b78d 100644 --- a/src/module/decode/p25/C4FMSymbolFilter.java +++ b/src/module/decode/p25/C4FMSymbolFilter.java @@ -9,21 +9,21 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sample.Listener; import sample.real.RealBuffer; import sample.real.RealSampleListener; import sample.real.RealSampleProvider; +import source.tuner.TunerChannelSource; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.IFrequencyChangeListener; import dsp.gain.DirectGainControl; -public class C4FMSymbolFilter implements Listener, - IFrequencyChangeListener, - RealSampleProvider, - Instrumentable +public class C4FMSymbolFilter implements Listener, IFrequencyChangeListener, RealSampleProvider, Instrumentable { - private static final float TAPS[][] = + private static final float TAPS[][] = { { 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128 { -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128 @@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener, private RealSampleListener mListener; - private DirectGainControl mGainController = - new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f ); + private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f ); private int mFrequencyAdjustmentRequested = 0; private int mFrequencyCorrection = 0; @@ -258,9 +257,8 @@ public class C4FMSymbolFilter implements Listener, correction = -mFrequencyCorrectionMaximum; } - broadcast( new FrequencyChangeEvent( - Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) ); - + broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) ); + mFrequencyAdjustmentRequested = 0; } } @@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener, } } + /** + * Broadcasts a frequency change event to the registered listener + * @param event + */ public void broadcast( FrequencyChangeEvent event ) { if( mFrequencyChangeListener != null ) @@ -459,7 +461,11 @@ public class C4FMSymbolFilter implements Listener, mFrequencyChangeListener.receive( event ); } } - + + /** + * Sets the listener for frequency change events. + * @param listener + */ public void setFrequencyChangeListener( Listener listener ) { mFrequencyChangeListener = listener; diff --git a/src/module/decode/p25/P25Decoder.java b/src/module/decode/p25/P25Decoder.java index 184c6f88..0ca1f8a9 100644 --- a/src/module/decode/p25/P25Decoder.java +++ b/src/module/decode/p25/P25Decoder.java @@ -4,8 +4,10 @@ import alias.AliasList; import instrument.Instrumentable; import module.decode.Decoder; 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; diff --git a/src/module/decode/p25/P25MessageFramer.java b/src/module/decode/p25/P25MessageFramer.java index 39d9bb80..30af300b 100644 --- a/src/module/decode/p25/P25MessageFramer.java +++ b/src/module/decode/p25/P25MessageFramer.java @@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener private LSMDemodulator mDemodulator; private double mCorrection; - public CostasPhaseErrorDetector( FrameSync frameSync, - LSMDemodulator demodulator, double correction ) + public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction ) { super( frameSync.getSync() ); diff --git a/src/module/decode/p25/P25_C4FMDecoder.java b/src/module/decode/p25/P25_C4FMDecoder.java index 94f2be56..2e7b1114 100644 --- a/src/module/decode/p25/P25_C4FMDecoder.java +++ b/src/module/decode/p25/P25_C4FMDecoder.java @@ -1,19 +1,20 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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 + * 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 + * ******************************************************************************/ package module.decode.p25; @@ -41,8 +42,7 @@ import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; import dsp.filter.fir.real.RealFIRFilter_RB_RB; -public class P25_C4FMDecoder extends P25Decoder - implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener +public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener { private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class ); diff --git a/src/module/decode/p25/P25_LSMDecoder.java b/src/module/decode/p25/P25_LSMDecoder.java index 011ff83d..94521323 100644 --- a/src/module/decode/p25/P25_LSMDecoder.java +++ b/src/module/decode/p25/P25_LSMDecoder.java @@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; import dsp.gain.ComplexFeedForwardGainControl; import dsp.psk.LSMDemodulator; import dsp.psk.QPSKPolarSlicer; +import source.tuner.frequency.FrequencyChangeEvent; public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener { @@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener private List mAvailableTaps; private ComplexFIRFilter_CB_CB mBasebandFilter; - private ComplexBufferToStreamConverter mStreamConverter = - new ComplexBufferToStreamConverter(); + private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter(); private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl( 32 ); private LSMDemodulator mLSMDemodulator = new LSMDemodulator(); @@ -107,8 +107,24 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener mMessageFramer.dispose(); mMessageFramer = null; } - - @Override + + @Override + public void setFrequencyChangeListener(Listener listener) + { + } + + @Override + public void removeFrequencyChangeListener() + { + } + + @Override + public Listener getFrequencyChangeListener() + { + return null; + } + + @Override public Listener getComplexBufferListener() { return mBasebandFilter; diff --git a/src/source/Source.java b/src/source/Source.java index 2de310b8..932c2749 100644 --- a/src/source/Source.java +++ b/src/source/Source.java @@ -20,11 +20,13 @@ package source; import module.Module; import sample.SampleType; 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. */ -public abstract class Source extends Module +public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider { protected SampleType mSampleType; protected IOverflowListener mOverflowListener; diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java index 7ea8dd7b..cfc96997 100644 --- a/src/source/mixer/RealMixerSource.java +++ b/src/source/mixer/RealMixerSource.java @@ -36,11 +36,11 @@ import sample.real.RealBuffer; import source.RealSource; import source.SourceException; import dsp.gain.AutomaticGainControl; +import source.tuner.frequency.FrequencyChangeEvent; public class RealMixerSource extends RealSource { - private final static Logger mLog = - LoggerFactory.getLogger( RealMixerSource.class ); + private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class ); private long mFrequency = 0; private int mBufferSize = 16384; @@ -48,13 +48,11 @@ public class RealMixerSource extends RealSource private BufferReader mBufferReader = new BufferReader(); private TargetDataLine mTargetDataLine; private AudioFormat mAudioFormat; - private String mDisplayName; private int mBytesPerFrame = 0; private ISampleAdapter mSampleAdapter; private AutomaticGainControl mAGC = new AutomaticGainControl(); - CopyOnWriteArrayList> mSampleListeners = - new CopyOnWriteArrayList>(); + CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>(); /** * Real Mixer Source - constructs a reader on the mixer/sound card target @@ -80,8 +78,27 @@ public class RealMixerSource extends RealSource mAudioFormat = format; mSampleAdapter = sampleAdapter; } - - @Override + + @Override + public void setFrequencyChangeListener(Listener listener) + { + //Not implemented + } + + @Override + public void removeFrequencyChangeListener() + { + //Not implemented + } + + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + @Override public void reset() { } @@ -133,23 +150,15 @@ public class RealMixerSource extends RealSource * buffer, otherwise send him a copy of the buffer */ if( it.hasNext() ) { -// next.receive( mAGC.process( samples.copyOf() ) ); next.receive( samples.copyOf() ); } else { -// next.receive( mAGC.process( samples ) ); next.receive( samples ); } } } - @Override - public String toString() - { - return mDisplayName; - } - public int getSampleRate() { if( mTargetDataLine != null ) diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index fa0cef46..7556e7b5 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -34,7 +34,9 @@ import source.ComplexSource; import source.SourceException; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent.Event; +import source.tuner.frequency.IFrequencyChangeListener; import source.tuner.frequency.IFrequencyChangeProcessor; +import source.tuner.frequency.IFrequencyChangeProvider; import java.util.ArrayList; import java.util.List; @@ -65,6 +67,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private ComplexPrimeCICDecimate mDecimationFilter; private Listener mListener; private IFrequencyChangeProcessor mFrequencyChangeProcessor; + private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor(); private ScheduledFuture mTaskHandle; private long mTunerFrequency = 0; @@ -75,7 +78,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private AtomicBoolean mRunning = new AtomicBoolean(); private boolean mExpended = false; - private int mCount; /** * 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()); /* 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)) { + //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 //to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention 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() { return mTuner; @@ -242,50 +263,49 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang case NOTIFICATION_FREQUENCY_CHANGE: mTunerFrequency = event.getValue().longValue(); updateMixerFrequencyOffset(); - break; - case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE: - mChannelFrequencyCorrection = event.getValue().intValue(); - updateMixerFrequencyOffset(); - - if(mFrequencyChangeProcessor != null) - { - mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent( - Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection)); - } + //Reset frequency correction so that downstream components can recalculate the value + setFrequencyCorrection(0); break; case NOTIFICATION_SAMPLE_RATE_CHANGE: int sampleRate = event.getValue().intValue(); - - if(mTunerSampleRate != sampleRate) - { - mMixer.setSampleRate(sampleRate); - - /* Get new decimation filter */ - mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1, - CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING); - - /* re-add the original output listener */ - mDecimationFilter.setListener(mListener); - - mTunerSampleRate = sampleRate; - } + 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) + { + mMixer.setSampleRate(sampleRate); + + /* Get new decimation filter */ + mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1, + CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING); + + /* re-add the original output listener */ + mDecimationFilter.setListener(mListener); + + mTunerSampleRate = sampleRate; + + mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate(); + } + } + /** * Calculates the local mixer frequency offset from the tuned frequency, * channel's requested frequency, and channel frequency correction. */ private void updateMixerFrequencyOffset() { - long offset = mTunerFrequency - - mTunerChannel.getFrequency() - - mChannelFrequencyCorrection; - + long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection; mMixer.setFrequency(offset); } @@ -299,6 +319,140 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang 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 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 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 + { + //Listener to receive downstream events + private Listener 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 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 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 * standard 48000 channel sample rate diff --git a/src/source/tuner/frequency/IFrequencyChangeProvider.java b/src/source/tuner/frequency/IFrequencyChangeProvider.java index 919126e7..354e9af9 100644 --- a/src/source/tuner/frequency/IFrequencyChangeProvider.java +++ b/src/source/tuner/frequency/IFrequencyChangeProvider.java @@ -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 + * + ******************************************************************************/ package source.tuner.frequency; import sample.Listener; +/** + * Provider of Frequency Change Events + */ public interface IFrequencyChangeProvider { - public void setFrequencyChangeListener( Listener listener ); - public void removeFrequencyChangeListener(); + void setFrequencyChangeListener( Listener listener ); + void removeFrequencyChangeListener(); } diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java index efa8b14b..8d80ce1d 100644 --- a/src/source/wave/ComplexWaveSource.java +++ b/src/source/wave/ComplexWaveSource.java @@ -1,19 +1,20 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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 + * 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 + * ******************************************************************************/ package source.wave; @@ -36,6 +37,7 @@ import sample.complex.ComplexBuffer; import source.ComplexSource; import source.IControllableFileSource; import source.IFrameLocationListener; +import source.tuner.frequency.FrequencyChangeEvent; public class ComplexWaveSource extends ComplexSource implements IControllableFileSource { @@ -54,8 +56,27 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil { mFile = file; } - - @Override + + @Override + public void setFrequencyChangeListener(Listener listener) + { + //Not implemented + } + + @Override + public void removeFrequencyChangeListener() + { + //Not implemented + } + + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + @Override public void reset() { stop(); diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java index 06e4fc25..4b9b120b 100644 --- a/src/source/wave/RealWaveSource.java +++ b/src/source/wave/RealWaveSource.java @@ -36,6 +36,7 @@ import sample.real.RealBuffer; import source.IControllableFileSource; import source.IFrameLocationListener; import source.RealSource; +import source.tuner.frequency.FrequencyChangeEvent; public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable @@ -55,8 +56,27 @@ public class RealWaveSource extends RealSource { mFile = file; } - - + + @Override + public void setFrequencyChangeListener(Listener listener) + { + //Not implemented + } + + @Override + public void removeFrequencyChangeListener() + { + //Not implemented + } + + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + @Override public void reset() { diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java index ae0c23fc..b064b804 100644 --- a/src/spectrum/OverlayPanel.java +++ b/src/spectrum/OverlayPanel.java @@ -50,6 +50,7 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ private static final long serialVersionUID = 1L; 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, RenderingHints.VALUE_ANTIALIAS_ON); @@ -187,7 +188,6 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ * 6 64x Zoom * * @param zoom level, 0 - 6. - * @param offset into the FFT bins for start zoom window */ 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 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); - //Vertical frequency error line - graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d)); + //Horizontal line connecting frequency and error line + graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisBottom, errorEdgeStop, verticalAxisBottom)); - //Horizontal line connecting frequency and error lines - graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight)); + //Vertical band edge lines + 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; - drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error)); + drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error), width, correction, + tunerChannel.getFrequency()); } } } diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java index d16b35a8..156ab50f 100644 --- a/src/spectrum/SpectrumPanel.java +++ b/src/spectrum/SpectrumPanel.java @@ -1,493 +1,483 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2015 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 + * 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 + * ******************************************************************************/ 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.NoSmoothingFilter; import dsp.filter.smoothing.RectangularSmoothingFilter; import dsp.filter.smoothing.SmoothingFilter; import dsp.filter.smoothing.SmoothingFilter.SmoothingType; 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 - implements DFTResultsListener, - SettingChangeListener, - SpectralDisplayAdjuster +import javax.swing.*; +import java.awt.*; +import java.awt.geom.GeneralPath; +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 = - LoggerFactory.getLogger( SpectrumPanel.class ); - - private static final RenderingHints RENDERING_HINTS = - new RenderingHints( RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); + private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class); - static - { - RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY ); - } - - private Color mColorSpectrumBackground; - private Color mColorSpectrumGradientTop; - private Color mColorSpectrumGradientBottom; - private Color mColorSpectrumLine; + private static final RenderingHints RENDERING_HINTS = + new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - //Defines the panel inset along the bottom for frequency display - private float mSpectrumInset = 20.0f; - - //Current DFT output bins in dB - private float[] mDisplayFFTBins = new float[ 1 ]; - - //Averaging across multiple DFT result sets - private int mAveraging = 4; - - //Smoothing across bins in the same DFT result set - private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter(); - - //Reference dB value set according to the source sample size - private float mDBScale; - - private int mZoom = 0; - private int mZoomWindowOffset = 0;; - - private SettingsManager mSettingsManager; - - public SpectrumPanel( SettingsManager settingsManager ) + static { - mSettingsManager = settingsManager; - - if( mSettingsManager != null ) - { - mSettingsManager.addListener( this ); - } - - setSampleSize( 16.0 ); - - mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT ); - - getColors(); - - setAveraging( mAveraging ); + RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); } - - public void dispose() - { - if( mSettingsManager != null ) - { - mSettingsManager.removeListener( this ); - } - - mSettingsManager = null; - } - + + private Color mColorSpectrumBackground; + private Color mColorSpectrumGradientTop; + private Color mColorSpectrumGradientBottom; + private Color mColorSpectrumLine; + + //Defines the panel inset along the bottom for frequency display + private float mSpectrumInset = 20.0f; + + //Current DFT output bins in dB + private float[] mDisplayFFTBins = new float[1]; + + //Averaging across multiple DFT result sets + private int mAveraging = 4; + + //Smoothing across bins in the same DFT result set + private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter(); + + //Reference dB value set according to the source sample size + private float mDBScale; + + private int mZoom = 0; + private int mZoomWindowOffset = 0; + ; + + private SettingsManager mSettingsManager; + + public SpectrumPanel(SettingsManager settingsManager) + { + mSettingsManager = settingsManager; + + if(mSettingsManager != null) + { + mSettingsManager.addListener(this); + } + + setSampleSize(16.0); + + mSmoothingFilter.setPointSize(SmoothingFilter.SMOOTHING_DEFAULT); + + getColors(); + + setAveraging(mAveraging); + } + + public void dispose() + { + if(mSettingsManager != null) + { + mSettingsManager.removeListener(this); + } + + mSettingsManager = null; + } + /** * DFTResultsListener interface for receiving the processed data * to display */ - public void receive( float[] currentFFTBins ) + public void receive(float[] currentFFTBins) { - //Prevent arrays of NaN values from being rendered. The first few - //DFT result sets on startup will contain NaN values - if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) ) - { - currentFFTBins = new float[ currentFFTBins.length ]; - } + //Prevent arrays of NaN values from being rendered. The first few + //DFT result sets on startup will contain NaN values + if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0])) + { + currentFFTBins = new float[currentFFTBins.length]; + } - //Construct and/or resize our DFT results variables - if( mDisplayFFTBins == null || - mDisplayFFTBins.length != currentFFTBins.length ) - { - mDisplayFFTBins = currentFFTBins; - } + //Construct and/or resize our DFT results variables + if(mDisplayFFTBins == null || + mDisplayFFTBins.length != currentFFTBins.length) + { + mDisplayFFTBins = currentFFTBins; + } - //Apply smoothing across the bins of the DFT results - float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins ); + //Apply smoothing across the bins of the DFT results + float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins); - //Apply averaging over multiple DFT output frames - if( mAveraging > 1 ) - { - float gain = 1.0f / (float)mAveraging; - - for( int x = 0; x < mDisplayFFTBins.length; x++ ) - { - mDisplayFFTBins[ x ] += - ( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain; - } - } - else - { - mDisplayFFTBins = smoothedBins; - } - - repaint(); + //Apply averaging over multiple DFT output frames + if(mAveraging > 1) + { + float gain = 1.0f / (float)mAveraging; + + for(int x = 0; x < mDisplayFFTBins.length; x++) + { + mDisplayFFTBins[x] += + (smoothedBins[x] - mDisplayFFTBins[x]) * gain; + } + } + else + { + mDisplayFFTBins = smoothedBins; + } + + repaint(); } @Override - public void paintComponent( Graphics g ) + public void paintComponent(Graphics g) { - super.paintComponent( g ); - - Graphics2D graphics = (Graphics2D) g; + super.paintComponent(g); - graphics.setBackground( mColorSpectrumBackground ); + Graphics2D graphics = (Graphics2D)g; - graphics.setRenderingHints( RENDERING_HINTS ); - - drawSpectrum( graphics ); + graphics.setBackground(mColorSpectrumBackground); + + graphics.setRenderingHints(RENDERING_HINTS); + + drawSpectrum(graphics); } - + /** * Draws the current fft spectrum with a line and a gradient fill. */ - private void drawSpectrum( Graphics2D graphics ) + private void drawSpectrum(Graphics2D graphics) { - Dimension size = getSize(); + Dimension size = getSize(); - //Draw the background - Rectangle background = new Rectangle( 0, 0, size.width, size.height ); - graphics.setColor( mColorSpectrumBackground ); - graphics.draw( background ); - graphics.fill( background ); + //Draw the background + Rectangle background = new Rectangle(0, 0, size.width, size.height); + graphics.setColor(mColorSpectrumBackground); + graphics.draw(background); + graphics.fill(background); - //Define the gradient - GradientPaint gradient = new GradientPaint( 0, - ( getSize().height - mSpectrumInset ) / 2, - mColorSpectrumGradientTop, - 0, - getSize().height, - mColorSpectrumGradientBottom ); - - graphics.setBackground( mColorSpectrumBackground ); - - GeneralPath spectrumShape = new GeneralPath(); + //Define the gradient + GradientPaint gradient = new GradientPaint(0, + (getSize().height - mSpectrumInset) / 2, + mColorSpectrumGradientTop, + 0, + getSize().height, + mColorSpectrumGradientBottom); - //Start at the lower right inset point - spectrumShape.moveTo( size.getWidth(), - size.getHeight() - mSpectrumInset ); - - //Draw to the lower left - spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset ); + graphics.setBackground(mColorSpectrumBackground); - float[] bins = getBins(); - - //If we have FFT data to display ... - if( bins != null ) - { - float insideHeight = size.height - mSpectrumInset; + GeneralPath spectrumShape = new GeneralPath(); - float scalor = insideHeight / -mDBScale; + //Start at the lower right inset point + spectrumShape.moveTo(size.getWidth(), + size.getHeight() - mSpectrumInset); + + //Draw to the lower left + spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset); + + float[] bins = getBins(); + + //If we have FFT data to display ... + if(bins != null) + { + float insideHeight = size.height - mSpectrumInset; + + float scalor = insideHeight / -mDBScale; /* Calculate based on bin size - 1, since bin 0 is rendered at zero - * and the last bin is rendered at the width */ - float binSize = (float)size.width / ( (float)( bins.length ) ); + * and the last bin is rendered at the width */ + float binSize = (float)size.width / ((float)(bins.length)); - for( int x = 0; x < bins.length; x++ ) - { - float height; - - height = bins[ x ] * scalor; - - if( height > insideHeight ) - { - height = insideHeight; - } - - if( height < 0 ) - { - height = 0; - } - - float xAxis = (float)x * binSize; + for(int x = 0; x < bins.length; x++) + { + float height; - spectrumShape.lineTo( xAxis, height ); - } - } - //Otherwise show an empty spectrum - else - { - //Draw Left Size - graphics.setPaint( gradient ); - spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset ); - //Draw Middle - spectrumShape.lineTo( size.getWidth(), - size.getHeight() - mSpectrumInset ); - } - - //Draw Right Side - spectrumShape.lineTo( size.getWidth(), - size.getHeight() - mSpectrumInset ); - - graphics.setPaint( gradient ); - graphics.draw( spectrumShape ); - graphics.fill( spectrumShape ); - - graphics.setPaint( mColorSpectrumLine ); + height = bins[x] * scalor; - //Draw the bottom line under the spectrum - graphics.draw( new Line2D.Float( 0, - size.height - mSpectrumInset, - size.width, - size.height - mSpectrumInset ) ); + if(height > insideHeight) + { + height = insideHeight; + } + + if(height < 0) + { + height = 0; + } + + float xAxis = (float)x * binSize; + + spectrumShape.lineTo(xAxis, height); + } + } + //Otherwise show an empty spectrum + else + { + //Draw Left Size + graphics.setPaint(gradient); + spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset); + //Draw Middle + spectrumShape.lineTo(size.getWidth(), + size.getHeight() - mSpectrumInset); + } + + //Draw Right Side + spectrumShape.lineTo(size.getWidth(), + size.getHeight() - mSpectrumInset); + + graphics.setPaint(gradient); + graphics.draw(spectrumShape); + graphics.fill(spectrumShape); + + graphics.setPaint(mColorSpectrumLine); + + //Draw the bottom line under the spectrum + graphics.draw(new Line2D.Float(0, + size.height - mSpectrumInset, + size.width, + size.height - mSpectrumInset)); } /** * Sets the current zoom level - * + * * 0 No Zoom * 1 2x Zoom * 2 4x Zoom * 3 8x Zoom * 4 16x Zoom * 5 32x Zoom - * - * @param zoom level, 0 - 5. + * 6 64x Zoom + * + * @param zoom level, 0 - 6. */ - public void setZoom( int zoom ) + public void setZoom(int zoom) { - Validate.isTrue( 0 <= zoom && zoom <= 5 ); - - mZoom = zoom; + Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom); + + mZoom = zoom; } /** * Sets the offset to define which subset of zoomed bins to display - * + * * @param offset */ - public void setZoomWindowOffset( int offset ) + public void setZoomWindowOffset(int offset) { - mZoomWindowOffset = offset; + mZoomWindowOffset = offset; } - /** - * Sets the source sample size in bits which defines the lowest dB value to - * display in the spectrum - * - * @param sampleSize in bits - */ - public void setSampleSize( double sampleSize ) - { - Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0); - - mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) ); - } - - /** - * Defines the number of DFT result sets to average across - */ - public void setAveraging( int size ) - { - mAveraging = size; - } - - /** - * DFT result sets averaging value - */ - public int getAveraging() - { - return mAveraging; - } - - /** - * Clears the spectral display - */ - public void clearSpectrum() - { - mDisplayFFTBins = null; - - repaint(); - } - - private void getColors() - { - mColorSpectrumBackground = - getColor( ColorSettingName.SPECTRUM_BACKGROUND ); - - mColorSpectrumGradientBottom = - getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM ); - - mColorSpectrumGradientTop = - getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP ); - - mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE ); - } - - /** - * Retrieves the current setting for the named color setting - */ - private Color getColor( ColorSettingName name ) - { - ColorSetting setting = mSettingsManager.getColorSetting( name ); - - return setting.getColor(); - } - - /** - * Listener interface to receive setting change notifications - */ - @Override - public void settingChanged( Setting setting ) + /** + * Sets the source sample size in bits which defines the lowest dB value to + * display in the spectrum + * + * @param sampleSize in bits + */ + public void setSampleSize(double sampleSize) { - if( setting instanceof ColorSetting ) - { - ColorSetting colorSetting = (ColorSetting)setting; - - switch( ( (ColorSetting) setting ).getColorSettingName() ) - { - case SPECTRUM_BACKGROUND: - mColorSpectrumBackground = colorSetting.getColor(); - break; - case SPECTRUM_GRADIENT_BOTTOM: - mColorSpectrumGradientBottom = colorSetting.getColor(); - break; - case SPECTRUM_GRADIENT_TOP: - mColorSpectrumGradientTop = colorSetting.getColor(); - break; - case SPECTRUM_LINE: - mColorSpectrumLine = colorSetting.getColor(); - break; - default: - break; - } - } + Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0); + + mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1))); } - - private int getZoomMultiplier() - { - return (int)Math.pow( 2.0, mZoom ); - } - - /** - * Returns the DFT result bins, or a zoomed and offset version of the bins - * when the display is zoomed. - */ + + /** + * Defines the number of DFT result sets to average across + */ + public void setAveraging(int size) + { + mAveraging = size; + } + + /** + * DFT result sets averaging value + */ + public int getAveraging() + { + return mAveraging; + } + + /** + * Clears the spectral display + */ + public void clearSpectrum() + { + mDisplayFFTBins = null; + + repaint(); + } + + private void getColors() + { + mColorSpectrumBackground = + getColor(ColorSettingName.SPECTRUM_BACKGROUND); + + mColorSpectrumGradientBottom = + getColor(ColorSettingName.SPECTRUM_GRADIENT_BOTTOM); + + mColorSpectrumGradientTop = + getColor(ColorSettingName.SPECTRUM_GRADIENT_TOP); + + mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE); + } + + /** + * Retrieves the current setting for the named color setting + */ + private Color getColor(ColorSettingName name) + { + ColorSetting setting = mSettingsManager.getColorSetting(name); + + return setting.getColor(); + } + + /** + * Listener interface to receive setting change notifications + */ + @Override + public void settingChanged(Setting setting) + { + if(setting instanceof ColorSetting) + { + ColorSetting colorSetting = (ColorSetting)setting; + + switch(((ColorSetting)setting).getColorSettingName()) + { + case SPECTRUM_BACKGROUND: + mColorSpectrumBackground = colorSetting.getColor(); + break; + case SPECTRUM_GRADIENT_BOTTOM: + mColorSpectrumGradientBottom = colorSetting.getColor(); + break; + case SPECTRUM_GRADIENT_TOP: + mColorSpectrumGradientTop = colorSetting.getColor(); + break; + case SPECTRUM_LINE: + mColorSpectrumLine = colorSetting.getColor(); + break; + default: + break; + } + } + } + + private int getZoomMultiplier() + { + return (int)Math.pow(2.0, mZoom); + } + + /** + * Returns the DFT result bins, or a zoomed and offset version of the bins + * when the display is zoomed. + */ private float[] getBins() { - if( mZoom == 0 ) - { - return mDisplayFFTBins; - } - else - { - int length = mDisplayFFTBins.length / getZoomMultiplier(); - - int offset = mZoomWindowOffset; - - if( ( offset + length ) >= mDisplayFFTBins.length ) - { - offset = mDisplayFFTBins.length - length; - } - - if( offset < 0 ) - { - offset = 0; - } + if(mZoom == 0) + { + return mDisplayFFTBins; + } + else + { + int length = mDisplayFFTBins.length / getZoomMultiplier(); - return Arrays.copyOfRange( mDisplayFFTBins, offset, offset + length ); - } + int offset = mZoomWindowOffset; + + if((offset + length) >= mDisplayFFTBins.length) + { + offset = mDisplayFFTBins.length - length; + } + + if(offset < 0) + { + offset = 0; + } + + return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length); + } } - @Override - public void settingDeleted( Setting setting ) { /* not implemented */ } + @Override + public void settingDeleted(Setting setting) + { /* not implemented */ } - /** - * Returns the current inter-bin smoothing setting - */ - @Override - public int getSmoothing() - { - return mSmoothingFilter.getPointSize(); - } + /** + * Returns the current inter-bin smoothing setting + */ + @Override + public int getSmoothing() + { + return mSmoothingFilter.getPointSize(); + } - /** - * Sets the bin smoothing value to define how wide the smoothing window is - * when averaging across DFT bins - */ - @Override - public void setSmoothing( int smoothing ) - { - mSmoothingFilter.setPointSize( smoothing ); - } + /** + * Sets the bin smoothing value to define how wide the smoothing window is + * when averaging across DFT bins + */ + @Override + public void setSmoothing(int smoothing) + { + mSmoothingFilter.setPointSize(smoothing); + } - /** - * Returns the current smoothing filter type - */ - @Override - public SmoothingType getSmoothingType() - { - return mSmoothingFilter.getSmoothingType(); - } + /** + * Returns the current smoothing filter type + */ + @Override + public SmoothingType getSmoothingType() + { + return mSmoothingFilter.getSmoothingType(); + } - /** - * Sets the smoothing filter type - */ - @Override - public void setSmoothingType( SmoothingType type ) - { - if( mSmoothingFilter.getSmoothingType() != type ) - { - int pointSize = getSmoothing(); + /** + * Sets the smoothing filter type + */ + @Override + public void setSmoothingType(SmoothingType type) + { + if(mSmoothingFilter.getSmoothingType() != type) + { + int pointSize = getSmoothing(); - synchronized ( mSmoothingFilter ) - { - switch( type ) - { - case GAUSSIAN: - mSmoothingFilter = new GaussianSmoothingFilter(); - break; - case RECTANGLE: - mSmoothingFilter = new RectangularSmoothingFilter(); - break; - case TRIANGLE: - mSmoothingFilter = new TriangularSmoothingFilter(); - break; - case NONE: - default: - mSmoothingFilter = new NoSmoothingFilter(); - break; - } - } - - setSmoothing( pointSize ); - } - } + synchronized(mSmoothingFilter) + { + switch(type) + { + case GAUSSIAN: + mSmoothingFilter = new GaussianSmoothingFilter(); + break; + case RECTANGLE: + mSmoothingFilter = new RectangularSmoothingFilter(); + break; + case TRIANGLE: + mSmoothingFilter = new TriangularSmoothingFilter(); + break; + case NONE: + default: + mSmoothingFilter = new NoSmoothingFilter(); + break; + } + } + + setSmoothing(pointSize); + } + } }