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/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java index 72a869d0..95bf15ce 100644 --- a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java +++ b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java @@ -1,408 +1,412 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 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 dsp.filter.cic; -import java.util.ArrayList; -import java.util.List; - +import dsp.filter.FilterFactory; +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 sample.Listener; import sample.complex.ComplexBuffer; import sample.complex.ComplexSampleListener; import sample.complex.ComplexToComplexBufferAssembler; import sample.decimator.ComplexDecimator; -import dsp.filter.FilterFactory; -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 java.util.ArrayList; +import java.util.List; public class ComplexPrimeCICDecimate implements Listener { - public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, - 59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151, - 157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251, - 257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359, - 367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463, - 467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593, - 599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 }; - - private ArrayList mDecimatingStages = - new ArrayList(); - - private DecimatingStage mFirstDecimatingStage; + public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, + 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, + 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, + 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, + 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, + 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691}; - private Output mOutput; + private ArrayList mDecimatingStages = + new ArrayList(); - /** - * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and - * decimated, single paired i/q float sample output. - * - * Implements the CIC filter described in Understanding Digital Signal - * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple - * decimating stages each with a prime factor decimation rate. Multiple - * stages are cascaded to achieve the overall decimation rate. - * - * This filter supports a maximum decimation rate of 700. This filter can - * be adapted to higher decimation rates by adding additional prime factors - * to the PRIMES array. - * - * @param decimation - overall decimation rate - * @param order - filter order - */ - public ComplexPrimeCICDecimate( int decimation, int order, - int passFrequency, int attenuation, WindowType windowType ) - { - Validate.isTrue(decimation <= 700); - + private DecimatingStage mFirstDecimatingStage; - List stageSizes = getPrimeFactors( decimation ); - - for( int x = 0; x < stageSizes.size(); x++ ) - { - DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order ); - - mDecimatingStages.add( stage ); - - if( x == 0 ) - { - /* Reference to first stage -- will receive all samples */ - mFirstDecimatingStage = stage; - } - else - { - /* Wire the current stage to the previous stage */ - mDecimatingStages.get( x - 1 ).setListener( stage ); - } - } - - mOutput = new Output( 48000, passFrequency, attenuation, windowType ); - - mDecimatingStages.get( mDecimatingStages.size() - 1 ) - .setListener( mOutput ); - } - - public void dispose() - { - for( DecimatingStage stage: mDecimatingStages ) - { - stage.dispose(); - } - - mDecimatingStages.clear(); - mDecimatingStages = null; - mFirstDecimatingStage = null; - - mOutput.dispose(); - mOutput = null; - } - - /** - * Adds a listener to receive the output of this CIC decimation filter - */ - public void setListener( Listener listener ) - { - mOutput.setListener( listener ); - } - - /** - * Removes listener from output of this CIC decimation filter - */ - public void removeListener() - { - mOutput.removeListener(); - } + private Output mOutput; - /** - * Calculates the prime factors of the decimation rate up to a maximum - * decimation rate of 700. If you wish to have decimation rates higher - * than 700, then add additional prime factors to the PRIMES array. - * - * @param decimation - integral decimation rate - * @return - ordered list (smallest to largest) of prime factors - */ - public static List getPrimeFactors( int decimation ) - { - ArrayList stages = new ArrayList(); - - int pointer = 0; - - while( decimation > 0 && pointer < PRIMES.length ) - { - int prime = PRIMES[ pointer ]; - - if( decimation % prime == 0 ) - { - stages.add( prime ); - - decimation /= prime; - } - else - { - pointer++; - } - } - - return stages; - } - - /** - * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. - */ - @Override - public void receive( ComplexBuffer buffer ) + /** + * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and + * decimated, single paired i/q float sample output. + * + * Implements the CIC filter described in Understanding Digital Signal + * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple + * decimating stages each with a prime factor decimation rate. Multiple + * stages are cascaded to achieve the overall decimation rate. + * + * This filter supports a maximum decimation rate of 700. This filter can + * be adapted to higher decimation rates by adding additional prime factors + * to the PRIMES array. + * + * @param decimation - overall decimation rate + * @param order - filter order + */ + public ComplexPrimeCICDecimate(int decimation, int order, + int passFrequency, int attenuation, WindowType windowType) { - if( mFirstDecimatingStage != null ) - { - float[] samples = buffer.getSamples(); - - for( int x = 0; x < samples.length; x += 2 ) - { - mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] ); - } - } - } - - /** - * Decimating stage combines multiple CIC stages with a decimator. The - * number of stages is indicated by the order value and the size indicates - * the decimation rate of this stage. - */ - public class DecimatingStage implements ComplexSampleListener - { - private ArrayList mStages = new ArrayList(); - private Stage mFirstStage; - private ComplexDecimator mDecimator; - - public DecimatingStage( int size, int order ) - { - for( int x = 0; x < order; x++ ) - { - Stage stage; - - if( size == 2 ) - { - stage = new TwoStage(); - } - else - { - stage = new Stage( size ); - } - - mStages.add( stage ); - - if( x == 0 ) - { - mFirstStage = stage; - } - else - { - mStages.get( x - 1 ).setListener( stage ); - } - } - - mDecimator = new ComplexDecimator( size ); - - mStages.get( mStages.size() - 1 ).setListener( mDecimator ); - } - - public void dispose() - { - for( Stage stage: mStages ) - { - stage.dispose(); - } - - mStages.clear(); - mDecimator.dispose(); - mDecimator = null; - mFirstStage = null; - mStages = null; - } + Validate.isTrue(decimation <= 700); - @Override - public void receive( float i, float q ) + + List stageSizes = getPrimeFactors(decimation); + + for(int x = 0; x < stageSizes.size(); x++) { - mFirstStage.receive( i, q ); - } - - public void setListener( ComplexSampleListener listener ) - { - mDecimator.setListener( listener ); - } - } - - /** - * Single non-decimating CIC stage component. Uses a circular buffer and a - * running average internally to implement the stage so that stage size has - * essentially no impact on the computational requirements of the stage - */ - public class Stage implements ComplexSampleListener - { - protected ComplexSampleListener mListener; - - private float[] mISamples; - private float[] mQSamples; - - protected float mISum; - protected float mQSum; - - private int mSamplePointer = 0; - private int mSize; - - protected float mGain; - - public Stage() - { - } + DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order); - public Stage( int size ) - { - mSize = size - 1; - - mISamples = new float[ mSize ]; - mQSamples = new float[ mSize ]; - - mGain = 1.0f / (float)size; - } - - public void dispose() - { - mListener = null; - } - - public void receive( float i, float q ) - { + mDecimatingStages.add(stage); + + if(x == 0) + { + /* Reference to first stage -- will receive all samples */ + mFirstDecimatingStage = stage; + } + else + { + /* Wire the current stage to the previous stage */ + mDecimatingStages.get(x - 1).setListener(stage); + } + } + + mOutput = new Output(48000, passFrequency, attenuation, windowType); + + mDecimatingStages.get(mDecimatingStages.size() - 1) + .setListener(mOutput); + } + + public void dispose() + { + for(DecimatingStage stage : mDecimatingStages) + { + stage.dispose(); + } + + mDecimatingStages.clear(); + mDecimatingStages = null; + mFirstDecimatingStage = null; + + mOutput.dispose(); + mOutput = null; + } + + /** + * Adds a listener to receive the output of this CIC decimation filter + */ + public void setListener(Listener listener) + { + mOutput.setListener(listener); + } + + /** + * Removes listener from output of this CIC decimation filter + */ + public void removeListener() + { + mOutput.removeListener(); + } + + /** + * Calculates the prime factors of the decimation rate up to a maximum + * decimation rate of 700. If you wish to have decimation rates higher + * than 700, then add additional prime factors to the PRIMES array. + * + * @param decimation - integral decimation rate + * @return - ordered list (smallest to largest) of prime factors + */ + public static List getPrimeFactors(int decimation) + { + ArrayList stages = new ArrayList(); + + int pointer = 0; + + while(decimation > 0 && pointer < PRIMES.length) + { + int prime = PRIMES[pointer]; + + if(decimation % prime == 0) + { + stages.add(prime); + + decimation /= prime; + } + else + { + pointer++; + } + } + + return stages; + } + + /** + * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. + */ + @Override + public void receive(ComplexBuffer buffer) + { + if(mFirstDecimatingStage != null) + { + float[] samples = buffer.getSamples(); + + for(int x = 0; x < samples.length; x += 2) + { + mFirstDecimatingStage.receive(samples[x], samples[x + 1]); + } + } + } + + /** + * Decimating stage combines multiple CIC stages with a decimator. The + * number of stages is indicated by the order value and the size indicates + * the decimation rate of this stage. + */ + public class DecimatingStage implements ComplexSampleListener + { + private ArrayList mStages = new ArrayList(); + private Stage mFirstStage; + private ComplexDecimator mDecimator; + + public DecimatingStage(int size, int order) + { + for(int x = 0; x < order; x++) + { + Stage stage; + + if(size == 2) + { + stage = new TwoStage(); + } + else + { + stage = new Stage(size); + } + + mStages.add(stage); + + if(x == 0) + { + mFirstStage = stage; + } + else + { + mStages.get(x - 1).setListener(stage); + } + } + + mDecimator = new ComplexDecimator(size); + + mStages.get(mStages.size() - 1).setListener(mDecimator); + } + + public void dispose() + { + for(Stage stage : mStages) + { + stage.dispose(); + } + + mStages.clear(); + mDecimator.dispose(); + mDecimator = null; + mFirstStage = null; + mStages = null; + } + + @Override + public void receive(float i, float q) + { + mFirstStage.receive(i, q); + } + + public void setListener(ComplexSampleListener listener) + { + mDecimator.setListener(listener); + } + } + + /** + * Single non-decimating CIC stage component. Uses a circular buffer and a + * running average internally to implement the stage so that stage size has + * essentially no impact on the computational requirements of the stage + */ + public class Stage implements ComplexSampleListener + { + protected ComplexSampleListener mListener; + + private float[] mISamples; + private float[] mQSamples; + + protected float mISum; + protected float mQSum; + + private int mSamplePointer = 0; + private int mSize; + + protected float mGain; + + public Stage() + { + } + + public Stage(int size) + { + mSize = size - 1; + + mISamples = new float[mSize]; + mQSamples = new float[mSize]; + + mGain = 1.0f / (float)size; + } + + public void dispose() + { + mListener = null; + } + + public void receive(float i, float q) + { /* Subtract the oldest sample and add back in the newest */ - mISum = mISum - mISamples[ mSamplePointer ] + i; - mQSum = mQSum - mQSamples[ mSamplePointer ] + q; + mISum = mISum - mISamples[mSamplePointer] + i; + mQSum = mQSum - mQSamples[mSamplePointer] + q; /* Overwrite the oldest sample with the newest */ - mISamples[ mSamplePointer ] = i; - mQSamples[ mSamplePointer ] = q; - - mSamplePointer++; - - if( mSamplePointer >= mSize ) - { - mSamplePointer = 0; - } + mISamples[mSamplePointer] = i; + mQSamples[mSamplePointer] = q; - if( mListener != null ) - { - mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) ); - } - } - - public void setListener( ComplexSampleListener listener ) - { - mListener = listener; - } - } - - /** - * Size 2 stage that removes the unnecessary circular buffer management for - * a two-stage. - */ - public class TwoStage extends Stage - { - public TwoStage() - { - super(); - - mGain = 0.5f; - } - - public void receive( float i, float q ) - { - float iSum = mISum + i; - float qSum = mQSum + q; - - mISum = i; - mQSum = q; - - if( mListener != null ) - { - mListener.receive( ( iSum * mGain ), ( qSum * mGain ) ); - } - } - } - + mSamplePointer++; - /** - * Output adapter - applies gain correction and cleanup filter and broadcast - * to registered listener. - */ - public class Output implements ComplexSampleListener - { - /* Decimated output buffers will contain 1024 samples */ - private ComplexToComplexBufferAssembler mAssembler = - new ComplexToComplexBufferAssembler( 2048 ); + if(mSamplePointer >= mSize) + { + mSamplePointer = 0; + } - private ComplexFIRFilter_CB_CB mCleanupFilter; - private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false ); - - public Output( int outputSampleRate, int passFrequency, int attenuation, - WindowType windowType ) - { - mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory - .getCICCleanupFilter( outputSampleRate, - passFrequency, - attenuation, - windowType ), 0.4f ); - - mAssembler.setListener( mCleanupFilter ); - mCleanupFilter.setListener( mHalfBandFilter ); - } - - public void dispose() - { - mAssembler.dispose(); - mCleanupFilter.dispose(); - mHalfBandFilter.dispose(); - } - - /** - * Receiver method for the output adapter to receive a filtered, - * decimated sample, apply gain correction, apply cleanup filtering - * and output the sample values as a complex sample. - */ - @Override - public void receive( float inphase, float quadrature ) - { - mAssembler.receive( inphase, quadrature ); - } - - /** - * Adds a listener to receive output samples - */ - public void setListener( Listener listener ) - { - mHalfBandFilter.setListener( listener ); + if(mListener != null) + { + mListener.receive((mISum * mGain), (mQSum * mGain)); + } } - /** - * Removes the listener from receiving output samples - */ + public void setListener(ComplexSampleListener listener) + { + mListener = listener; + } + } + + /** + * Size 2 stage that removes the unnecessary circular buffer management for + * a two-stage. + */ + public class TwoStage extends Stage + { + public TwoStage() + { + super(); + + mGain = 0.5f; + } + + public void receive(float i, float q) + { + float iSum = mISum + i; + float qSum = mQSum + q; + + mISum = i; + mQSum = q; + + if(mListener != null) + { + mListener.receive((iSum * mGain), (qSum * mGain)); + } + } + } + + + /** + * Output adapter - applies gain correction and cleanup filter and broadcast + * to registered listener. + */ + public class Output implements ComplexSampleListener + { + /* Decimated output buffers will contain 1024 samples */ + private ComplexToComplexBufferAssembler mAssembler = + new ComplexToComplexBufferAssembler(2048); + + private ComplexFIRFilter_CB_CB mCleanupFilter; + private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB( + Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false); + + public Output(int outputSampleRate, int passFrequency, int attenuation, + WindowType windowType) + { + mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory + .getCICCleanupFilter(outputSampleRate, + passFrequency, + attenuation, + windowType), 0.4f); + + //Bypassing the CIC cleanup filter for now + mAssembler.setListener(mHalfBandFilter); + +// mAssembler.setListener( mCleanupFilter ); +// mCleanupFilter.setListener( mHalfBandFilter ); + } + + public void dispose() + { + mAssembler.dispose(); + mCleanupFilter.dispose(); + mHalfBandFilter.dispose(); + } + + /** + * Receiver method for the output adapter to receive a filtered, + * decimated sample, apply gain correction, apply cleanup filtering + * and output the sample values as a complex sample. + */ + @Override + public void receive(float inphase, float quadrature) + { + mAssembler.receive(inphase, quadrature); + } + + /** + * Adds a listener to receive output samples + */ + public void setListener(Listener listener) + { + mHalfBandFilter.setListener(listener); + } + + /** + * Removes the listener from receiving output samples + */ public void removeListener() { - mHalfBandFilter.removeListener(); + mHalfBandFilter.removeListener(); } - } + } } 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..db41a441 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,31 @@ 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 new Listener() + { + @Override + public void receive(FrequencyChangeEvent frequencyChangeEvent) + { + //Ignored + } + }; + } + + @Override public Listener getComplexBufferListener() { return mBasebandFilter; diff --git a/src/sample/Broadcaster.java b/src/sample/Broadcaster.java index 4fc1f819..d4f9cce1 100644 --- a/src/sample/Broadcaster.java +++ b/src/sample/Broadcaster.java @@ -58,6 +58,11 @@ public class Broadcaster implements Listener public void addListener( Listener listener ) { + if(listener == null) + { + throw new IllegalArgumentException("Listener cannot be null"); + } + mListeners.add( listener ); } 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); + } + } }