From 61ecb5abf2253fea755af78753b09e389c8ac3b4 Mon Sep 17 00:00:00 2001 From: "dennis.sheirer" Date: Sat, 11 Oct 2014 18:00:49 +0000 Subject: [PATCH] Adds an AM Demodulator to the list of available decoders. --- .classpath | 55 ++++---- src/controller/channel/ProcessingChain.java | 1 + src/controller/state/ChannelStateList.java | 10 +- src/decode/DecodeEditor.java | 23 ++-- src/decode/DecoderFactory.java | 12 +- src/decode/DecoderType.java | 4 +- src/decode/am/AMChannelState.java | 69 ++++++++++ src/decode/am/AMDecoder.java | 68 ++++++++++ src/decode/am/AMEditor.java | 31 +++++ src/decode/am/AMPanel.java | 136 ++++++++++++++++++++ src/decode/config/DecodeConfigAM.java | 35 +++++ src/decode/config/DecodeConfigFactory.java | 3 + src/decode/config/DecodeConfiguration.java | 8 +- src/dsp/am/AMDemodulator.java | 62 +++++++++ src/gui/SDRTrunk.java | 2 - 15 files changed, 472 insertions(+), 47 deletions(-) create mode 100644 src/decode/am/AMChannelState.java create mode 100644 src/decode/am/AMDecoder.java create mode 100644 src/decode/am/AMEditor.java create mode 100644 src/decode/am/AMPanel.java create mode 100644 src/decode/config/DecodeConfigAM.java create mode 100644 src/dsp/am/AMDemodulator.java diff --git a/.classpath b/.classpath index 413e9ff0..4039eba0 100644 --- a/.classpath +++ b/.classpath @@ -1,27 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java index b76bf591..9631d871 100644 --- a/src/controller/channel/ProcessingChain.java +++ b/src/controller/channel/ProcessingChain.java @@ -404,6 +404,7 @@ public class ProcessingChain implements Listener } if( mSource instanceof TunerChannelSource && + mChannel.getDecodeConfiguration().supportsAFC() && mChannel.getDecodeConfiguration().isAFCEnabled() ) { TunerChannelSource tcs = (TunerChannelSource)mSource; diff --git a/src/controller/state/ChannelStateList.java b/src/controller/state/ChannelStateList.java index 5d976bd2..8ed1566e 100644 --- a/src/controller/state/ChannelStateList.java +++ b/src/controller/state/ChannelStateList.java @@ -39,6 +39,8 @@ import controller.channel.Channel; import controller.channel.Channel.ChannelType; import controller.channel.ChannelEvent; import controller.channel.ChannelEventListener; +import decode.am.AMChannelState; +import decode.am.AMPanel; import decode.fleetsync2.FleetsyncChannelState; import decode.fleetsync2.FleetsyncPanel; import decode.ltrnet.LTRNetChannelState; @@ -139,8 +141,12 @@ public class ChannelStateList extends JPanel implements ChannelEventListener ChannelState state = channel.getProcessingChain().getChannelState(); ChannelStatePanel panel = null; - - if( state instanceof NBFMChannelState ) + + if( state instanceof AMChannelState ) + { + panel = new AMPanel( channel ); + } + else if( state instanceof NBFMChannelState ) { NBFMChannelState convState = (NBFMChannelState)state; diff --git a/src/decode/DecodeEditor.java b/src/decode/DecodeEditor.java index 99a3dac2..9825d225 100644 --- a/src/decode/DecodeEditor.java +++ b/src/decode/DecodeEditor.java @@ -37,18 +37,21 @@ public class DecodeEditor extends Editor { mConfig = config; - mAFC = new JCheckBox( "Automatic Frequency Control" ); - mAFC.setSelected( mConfig.isAFCEnabled() ); - mAFC.addActionListener( new ActionListener() + if( config.supportsAFC() ) { - @Override - public void actionPerformed( ActionEvent arg0 ) - { - mConfig.setAFC( mAFC.isSelected() ); - } - } ); + mAFC = new JCheckBox( "Automatic Frequency Control" ); + mAFC.setSelected( mConfig.isAFCEnabled() ); + mAFC.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + mConfig.setAFC( mAFC.isSelected() ); + } + } ); - add( mAFC, "wrap" ); + add( mAFC, "wrap" ); + } } public DecodeConfiguration getConfig() diff --git a/src/decode/DecoderFactory.java b/src/decode/DecoderFactory.java index 9673541d..62a07178 100644 --- a/src/decode/DecoderFactory.java +++ b/src/decode/DecoderFactory.java @@ -22,6 +22,8 @@ import alias.AliasList; import controller.channel.ChannelMap; import controller.channel.ProcessingChain; import controller.state.ChannelState; +import decode.am.AMChannelState; +import decode.am.AMDecoder; import decode.config.AuxDecodeConfiguration; import decode.config.DecodeConfigLTRNet; import decode.config.DecodeConfigLTRStandard; @@ -62,6 +64,9 @@ public class DecoderFactory switch( config.getDecoderType() ) { + case AM: + retVal = new AMDecoder( sampleType ); + break; case NBFM: retVal = new NBFMConventionalDecoder( sampleType ); break; @@ -128,12 +133,11 @@ public class DecoderFactory switch( decodeConfig.getDecoderType() ) { + case AM: + retVal = new AMChannelState( chain, aliasList ); + break; case NBFM: retVal = new NBFMChannelState( chain, aliasList ); - - DecodeConfigNBFM conv = - (DecodeConfigNBFM)decodeConfig; - break; case LTR_STANDARD: retVal = new LTRChannelState( chain, aliasList ); diff --git a/src/decode/DecoderType.java b/src/decode/DecoderType.java index 3695bada..d72c8680 100644 --- a/src/decode/DecoderType.java +++ b/src/decode/DecoderType.java @@ -25,6 +25,7 @@ import java.util.EnumSet; */ public enum DecoderType { + AM( "AM", "AM", "images/am", 10000 ), FLEETSYNC2( "Fleetsync II", "Fsync2", "images/fm", 12500 ), LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr_standard", 10000 ), LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net", 10000 ), @@ -53,7 +54,8 @@ public enum DecoderType */ public static EnumSet getAvailableDecoders() { - return EnumSet.of( DecoderType.LTR_NET, + return EnumSet.of( DecoderType.AM, + DecoderType.LTR_NET, DecoderType.LTR_STANDARD, DecoderType.MPT1327, DecoderType.NBFM, diff --git a/src/decode/am/AMChannelState.java b/src/decode/am/AMChannelState.java new file mode 100644 index 00000000..15a26a04 --- /dev/null +++ b/src/decode/am/AMChannelState.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 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 decode.am; + +import alias.AliasList; +import audio.SquelchListener; +import audio.SquelchListener.SquelchState; +import controller.channel.ProcessingChain; +import controller.state.AuxChannelState; +import controller.state.ChannelState; + +/** + * Conventional channel state - this class doesn't do anything at the moment, + * it is a place holder for future expansion for the conventional decoder + */ +public class AMChannelState extends ChannelState +{ + public AMChannelState( ProcessingChain channel, AliasList aliasList ) + { + super( channel, aliasList ); + } + + /** + * Make the ConventionalChannelState always unsquelched + */ + public void addListener( SquelchListener listener ) + { + super.addListener( listener ); + + super.setSquelchState( SquelchState.UNSQUELCH ); + } + + @Override + public void setSquelchState( SquelchState state ) + { + //do nothing ... we want the squelch state always unsquelched (for now) + } + @Override + public String getActivitySummary() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Activity Summary\n" ); + sb.append( "\tDecoder:\tAM\n\n" ); + + for( AuxChannelState state: mAuxChannelStates ) + { + sb.append( state.getActivitySummary() ); + sb.append( "\n\n" ); + } + + return sb.toString(); + } +} diff --git a/src/decode/am/AMDecoder.java b/src/decode/am/AMDecoder.java new file mode 100644 index 00000000..6cf33587 --- /dev/null +++ b/src/decode/am/AMDecoder.java @@ -0,0 +1,68 @@ +package decode.am; + +import sample.Listener; +import source.Source.SampleType; +import decode.Decoder; +import decode.DecoderType; +import dsp.am.AMDemodulator; +import dsp.filter.DCRemovalFilter2; + +public class AMDecoder extends Decoder +{ + /** + * This value determines how quickly the DC remove filter responds to + * changes in frequency. + */ + private static final double DC_REMOVAL_RATIO = 0.000003; + + private AMDemodulator mDemodulator; + private DCRemovalFilter2 mDCRemovalFilter; + + public AMDecoder( SampleType sampleType ) + { + super( sampleType ); + + /** + * Only setup a demod chain if we're receiving complex samples. If + * we're receiving demodulated samples, they'll be handled the same + * was as we handle the output of the demodulator. + */ + if( mSourceSampleType == SampleType.COMPLEX ) + { + /** + * The Decoder super class is both a Complex listener and a float + * listener. So, we add the demod to listen to the incoming + * quadrature samples, and we wire the output of the demod right + * back to this class, so we can receive the demodulated output + * to process + */ + mDemodulator = new AMDemodulator(); + + this.addComplexListener( mDemodulator ); + + /** + * Remove the DC component that is present when we're mistuned + */ + mDCRemovalFilter = new DCRemovalFilter2( DC_REMOVAL_RATIO ); + mDemodulator.addListener( mDCRemovalFilter ); + + /** + * Route the demodulated, filtered samples back to this class to send + * to all registered listeners + */ + mDCRemovalFilter.setListener( this.getFloatReceiver() ); + } + } + + @Override + public DecoderType getType() + { + return DecoderType.AM; + } + + @Override + public void addUnfilteredFloatListener( Listener listener ) + { + mDemodulator.addListener( listener ); + } +} diff --git a/src/decode/am/AMEditor.java b/src/decode/am/AMEditor.java new file mode 100644 index 00000000..6380a97f --- /dev/null +++ b/src/decode/am/AMEditor.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 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 decode.am; + +import decode.DecodeEditor; +import decode.config.DecodeConfiguration; + +public class AMEditor extends DecodeEditor +{ + private static final long serialVersionUID = 1L; + + public AMEditor( DecodeConfiguration config ) + { + super( config ); + } +} diff --git a/src/decode/am/AMPanel.java b/src/decode/am/AMPanel.java new file mode 100644 index 00000000..3842b807 --- /dev/null +++ b/src/decode/am/AMPanel.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 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 decode.am; + +import java.awt.EventQueue; + +import javax.swing.JLabel; + +import settings.ColorSetting; +import settings.Setting; +import controller.channel.Channel; +import controller.state.ChannelState.ChangedAttribute; +import controller.state.ChannelStatePanel; + +public class AMPanel extends ChannelStatePanel +{ + private static final long serialVersionUID = 1L; + + private JLabel mStateLabel; + private JLabel mSourceLabel; + private JLabel mChannelLabel; + + private JLabel mProtocol = new JLabel( "AM" ); + + public AMPanel( Channel channel ) + { + super( channel ); + + init(); + } + + public void init() + { + mStateLabel = new JLabel( mChannel.getProcessingChain(). + getChannelState().getState().getDisplayValue() ); + mStateLabel.setFont( mFontDecoder ); + mStateLabel.setForeground( mColorLabelDecoder ); + + mSourceLabel = new JLabel( mChannel.getSourceConfiguration().getDescription() ); + mSourceLabel.setFont( mFontDetails ); + mSourceLabel.setForeground( mColorLabelDetails ); + + mChannelLabel = new JLabel( mChannel.getChannelDisplayName() ); + mChannelLabel.setFont( mFontDetails ); + mChannelLabel.setForeground( mColorLabelDetails ); + + mProtocol.setFont( mFontDecoder ); + mProtocol.setForeground( mColorLabelDecoder ); + + add( mStateLabel ); + add( mSourceLabel ); + add( mChannelLabel, "wrap" ); + + add( mProtocol, "wrap" ); + } + + @Override + public void receive( final ChangedAttribute changedAttribute ) + { + EventQueue.invokeLater( new Runnable() + { + @Override + public void run() + { + switch( changedAttribute ) + { + case CHANNEL_STATE: + mStateLabel.setText( mChannel.getProcessingChain(). + getChannelState().getState().getDisplayValue() ); + break; + case SOURCE: + mSourceLabel.setText( mChannel.getSourceConfiguration() + .getDescription() ); + break; + case CHANNEL_NAME: + case SITE_NAME: + case SYSTEM_NAME: + mChannelLabel.setText( mChannel.getChannelDisplayName() ); + break; + } + + repaint(); + } + }); + } + + @Override + public void settingChanged( Setting setting ) + { + super.settingChanged( setting ); + + if( setting instanceof ColorSetting ) + { + ColorSetting colorSetting = (ColorSetting)setting; + + switch( colorSetting.getColorSettingName() ) + { + case CHANNEL_STATE_LABEL_DECODER: + if( mStateLabel != null ) + { + mStateLabel.setForeground( mColorLabelDecoder ); + } + if( mProtocol != null ) + { + mProtocol.setForeground( mColorLabelDecoder ); + } + break; + case CHANNEL_STATE_LABEL_DETAILS: + if( mSourceLabel != null ) + { + mSourceLabel.setForeground( mColorLabelDetails ); + } + if( mChannelLabel != null ) + { + mChannelLabel.setForeground( mColorLabelDetails ); + } + break; + } + } + } +} diff --git a/src/decode/config/DecodeConfigAM.java b/src/decode/config/DecodeConfigAM.java new file mode 100644 index 00000000..1ba26b68 --- /dev/null +++ b/src/decode/config/DecodeConfigAM.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 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 decode.config; + +import decode.DecoderType; + +public class DecodeConfigAM extends DecodeConfiguration +{ + public DecodeConfigAM() + { + super( DecoderType.AM ); + + setAFC( false ); + } + + public boolean supportsAFC() + { + return false; + } +} diff --git a/src/decode/config/DecodeConfigFactory.java b/src/decode/config/DecodeConfigFactory.java index 723d5ffd..9bbc7d2f 100644 --- a/src/decode/config/DecodeConfigFactory.java +++ b/src/decode/config/DecodeConfigFactory.java @@ -33,6 +33,9 @@ public class DecodeConfigFactory switch( decoder ) { + case AM: + retVal = new DecodeConfigAM(); + break; case NBFM: retVal = new DecodeConfigNBFM(); break; diff --git a/src/decode/config/DecodeConfiguration.java b/src/decode/config/DecodeConfiguration.java index b4cc4db1..e99e2ea9 100644 --- a/src/decode/config/DecodeConfiguration.java +++ b/src/decode/config/DecodeConfiguration.java @@ -24,7 +24,8 @@ import javax.xml.bind.annotation.XmlSeeAlso; import controller.config.Configuration; import decode.DecoderType; -@XmlSeeAlso( { DecodeConfigNBFM.class, +@XmlSeeAlso( { DecodeConfigAM.class, + DecodeConfigNBFM.class, DecodeConfigLTRNet.class, DecodeConfigLTRStandard.class, DecodeConfigMPT1327.class, @@ -65,4 +66,9 @@ public abstract class DecodeConfiguration extends Configuration { mAFCEnabled = enabled; } + + public boolean supportsAFC() + { + return true; + } } diff --git a/src/dsp/am/AMDemodulator.java b/src/dsp/am/AMDemodulator.java new file mode 100644 index 00000000..f70fd318 --- /dev/null +++ b/src/dsp/am/AMDemodulator.java @@ -0,0 +1,62 @@ +package dsp.am; + +import sample.Broadcaster; +import sample.Listener; +import sample.complex.ComplexSample; +import dsp.filter.ComplexFIRFilter; +import dsp.filter.FilterFactory; +import dsp.filter.Window.WindowType; + +public class AMDemodulator implements Listener +{ + private Broadcaster mBroadcaster = new Broadcaster(); + private ComplexFIRFilter mIQFilter; + + public AMDemodulator() + { + this( FilterFactory.getLowPass( 48000, 2000, 73, WindowType.HAMMING ), + 1000.0 ); + } + + public AMDemodulator( double[] iqFilter, double iqGain ) + { + mIQFilter = new ComplexFIRFilter( iqFilter, iqGain ); + + mIQFilter.setListener( new Demodulator() ); + } + + @Override + public void receive( ComplexSample sample ) + { + mIQFilter.receive( sample ); + } + + /** + * Adds a listener to receive demodulated samples + */ + public void addListener( Listener listener ) + { + mBroadcaster.addListener( listener ); + } + + /** + * Removes a listener from receiving demodulated samples + */ + public void removeListener( Listener listener ) + { + mBroadcaster.removeListener( listener ); + } + + public class Demodulator implements Listener + { + @Override + public void receive( ComplexSample sample ) + { + float demodulated = (float)Math.abs( + Math.sqrt( ( sample.real() * sample.real() ) + + ( sample.imaginery() * sample.imaginery() ) ) ); + + mBroadcaster.broadcast( demodulated * 30.0f ); + } + } +} diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index a1c229ba..5c99989c 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -17,7 +17,6 @@ ******************************************************************************/ package gui; - import java.awt.AWTException; import java.awt.Dimension; import java.awt.EventQueue; @@ -29,7 +28,6 @@ import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Date; import javax.imageio.ImageIO; import javax.swing.JFrame;