001    package edu.nrao.sss.electronics;
002    
003    import edu.nrao.sss.math.MathUtil;
004    import edu.nrao.sss.measure.Frequency;
005    import edu.nrao.sss.measure.FrequencyRange;
006    
007    /**
008     * A filter that operates on the current frequencies of {@link Signal signals}.
009     * Filters can only narrow the range of frequencies of the signals
010     * that pass through them.  Filters perform no other transformations.
011     * When a filter narrows the current frequency range of a signal, it
012     * also narrows the proxied range in a consistent manner.
013     * <p>
014     * <b>Version Info:</b>
015     * <table style="margin-left:2em">
016     *   <tr><td>$Revision: 1241 $</td></tr>
017     *   <tr><td>$Date: 2008-04-25 14:37:37 -0600 (Fri, 25 Apr 2008) $</td></tr>
018     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
019     * </table></p>
020     * 
021     * @author David M. Harland
022     * @since 2007-10-24
023     */
024    public class SignalFilter
025      implements SignalProcessor, SignalSource
026    {
027      private String name;
028    
029      private FrequencyRange passBand;
030      
031      private Signal inputSignal;
032      private Signal outputSignal;
033      
034      private SignalPipe inputPipe;
035      private SignalPipe outputPipe;
036    
037      //============================================================================
038      // OBJECT CREATION
039      //============================================================================
040    
041      /** Makes a new filter with the given pass band. */
042      private SignalFilter(String deviceName, FrequencyRange allowableFrequencies)
043      {
044        name = (deviceName == null) ? allowableFrequencies.toString()
045                                    : deviceName;
046        
047        passBand     = allowableFrequencies;
048        inputSignal  = null;
049        outputSignal = null;
050        inputPipe    = SignalPipe.makeAndWeldOutputTo(this);
051        outputPipe   = SignalPipe.makeAndWeldInputTo(this);
052      }
053      
054      /**
055       * Creates and returns a new filter that blocks all frequencies
056       * below {@code minFreqGHz}.
057       * 
058       * @param deviceName
059       *   an optional name for this filter.  If this value is <i>null</i>
060       *   a default name will be used in its place.
061       *   
062       * @param minFreqGHz
063       *   the lowest frequency, in gigahertz, that may pass through this filter.
064       *   
065       * @return a new high pass filter.
066       */
067      public static SignalFilter makeHighPass(String deviceName, String minFreqGHz)
068      {
069        return new SignalFilter(deviceName,
070                                new FrequencyRange(new Frequency(minFreqGHz),
071                                                   new Frequency(MathUtil.POSITIVE_INFINITY)));
072      }
073      
074      /**
075       * Creates and returns a new filter that blocks all frequencies
076       * above {@code maxFreqGHz}.
077       * 
078       * @param deviceName
079       *   an optional name for this filter.  If this value is <i>null</i>
080       *   a default name will be used in its place.
081       * 
082       * @param maxFreqGHz
083       *   the highest frequency, in gigahertz, that may pass through this filter.
084       *   
085       * @return a new low pass filter.
086       */
087      public static SignalFilter makeLowPass(String deviceName, String maxFreqGHz)
088      {
089        return new SignalFilter(deviceName,
090                                new FrequencyRange(new Frequency("0.0"),
091                                                   new Frequency(maxFreqGHz)));
092      }
093      
094      /**
095       * Creates and returns a new filter that blocks all frequencies
096       * outside the given range.
097       * 
098       * @param deviceName
099       *   an optional name for this filter.  If this value is <i>null</i>
100       *   a default name will be used in its place.
101       * 
102       * @param minFreqGHz
103       *   the lowest frequency, in gigahertz, that may pass through this filter.
104       *   
105       * @param maxFreqGHz
106       *   the highest frequency, in gigahertz, that may pass through this filter.
107       *   
108       * @return a new filter.
109       */
110      public static SignalFilter make(String deviceName,
111                                      String minFreqGHz, String maxFreqGHz)
112      {
113        return new SignalFilter(deviceName,
114                                new FrequencyRange(new Frequency(minFreqGHz),
115                                                   new Frequency(maxFreqGHz)));
116      }
117      
118      /**
119       * Creates and returns a new filter that blocks all frequencies
120       * outside the given range.
121       * 
122       * @param deviceName
123       *   an optional name for this filter.  If this value is <i>null</i>
124       *   a default name will be used in its place.
125       * 
126       * @param passingRange
127       *   the range of frequencies that may pass through this filter.
128       *   The endpoints of this range may both pass through this filter.
129       *   
130       * @return a new filter.
131       */
132      public static SignalFilter make(String deviceName,
133                                      FrequencyRange passingRange)
134      {
135        return new SignalFilter(deviceName, passingRange.clone());
136      }
137    
138      //============================================================================
139      // 
140      //============================================================================
141    
142      /**
143       * Returns the range of frequencies that may pass through this filter.
144       * @return the range of frequencies that may pass through this filter.
145       */
146      public FrequencyRange getPassBand()
147      {
148        return passBand.clone();
149      }
150      
151      /**
152       * Returns the input pipe of this device.
153       * A typical usage pattern for this method is:<pre>
154       * myFilter.getInputPipe().connectInputTo(mySource);</pre>
155       * 
156       * @return the input pipe of this device.
157       */
158      public SignalPipe getInputPipe()
159      {
160        return inputPipe;
161      }
162      
163      /**
164       * Returns the output pipe of this device.
165       * A typical usage pattern for this method is:<pre>
166       * myFilter.getOutputPipe().connectOutputTo(myProcessor);</pre>
167       * 
168       * @return the output pipe of this device.
169       */
170      public SignalPipe getOutputPipe()
171      {
172        return outputPipe;
173      }
174      
175      /**
176       * Returns a copy of the input signal most recently {@link #execute()
177       * processed} by this device.
178       * If this processor has never been executed, or if the input signal
179       * it processed most recently was <i>null</i>, the returned value will
180       * be <i>null</i>.
181       * 
182       * @return the input signal most recently sent into this filter.
183       */
184      public Signal getMostRecentInput()
185      {
186        return inputSignal == null ? null : inputSignal.clone();
187      }
188      
189      /**
190       * Returns a copy of the output signal most recently {@link #execute()
191       * produced} by this device.
192       * If this processor has never been executed, or if the input signal
193       * it processed most recently was <i>null</i>, the returned value will
194       * be <i>null</i>.
195       * 
196       * @return the output signal most recently produced by this filter.
197       */
198      public Signal getMostRecentOutput()
199      {
200        return outputSignal == null ? null : outputSignal.clone();
201      }
202    
203      /**
204       * Erases this device's memory of its most recent execution.
205       * @see #getMostRecentInput()
206       * @see #getMostRecentOutput()
207       * @see #getSignal()
208       */
209      public void eraseSignalMemory()
210      {
211        inputSignal  = null;
212        outputSignal = null;
213      }
214    
215      //============================================================================
216      // INTERFACE SignalSource
217      //============================================================================
218      
219      /**
220       * Returns a copy of the signal produced by the most recent {@link #execute()
221       * execution} of this device.
222       * If this device has never been run, or if the input signal
223       * it processed most recently was <i>null</i>, the returned value will
224       * be <i>null</i>.
225       */
226      public Signal getSignal()
227      {
228        return outputSignal == null ? null : outputSignal.clone();
229      }
230    
231      //============================================================================
232      // INTERFACE SignalProcessor AND SUPPORT THEREOF
233      //============================================================================
234      
235      /**
236       * Runs this device on the signal received from its input pipe and
237       * then executes the processor connected to its output pipe, if any.
238       * Both the input signal and the output produced by filtering it are
239       * remembered by this filter and are available via the
240       * {@link #getMostRecentInput()} and {@link #getMostRecentOutput()} methods.
241       */
242      public void execute()
243      {
244        updateOutputSignal();
245        
246        outputPipe.execute();
247      }
248      
249      public void executeUpTo(SignalProcessor firstUnexecutedDevice)
250      {
251        //Quick exit if execution should not occur
252        if (firstUnexecutedDevice == this)
253          return;
254    
255        updateOutputSignal();
256        
257        //Tell text element of chain to do its work
258        outputPipe.executeUpTo(firstUnexecutedDevice);
259      }
260      
261      private void updateOutputSignal()
262      {
263        //Do the work and remember results
264        inputSignal = inputPipe.getSignal();
265        
266        if (inputSignal != null)
267        {
268          outputSignal = filter(inputSignal);
269          
270          if (!outputSignal.getDevicePath().endsWith(name))
271            outputSignal.appendToDevicePath(name);
272        }
273        else
274        {
275          outputSignal = null;
276        }
277      }
278      
279      public void executeFromStartOfChainUpTo(SignalProcessor firstUnexecutedDevice)
280      {
281        inputPipe.executeFromStartOfChainUpTo(firstUnexecutedDevice);
282      }
283    
284      /**
285       * Creates and returns a new signal that is the result of applying this
286       * filter to the given input signal.
287       * <p>
288       * Note that the {@code input} signal and this filter are
289       * not affected by calls to this method.  In other words this filter
290       * will not remember the input signal sent in or the output
291       * signal produced.  This is in contrast to a call to the
292       * {@link #execute()} methods.</p>
293       * 
294       * @param input
295       *   the signal to be filtered.
296       *   
297       * @return a new signal that is the result of applying this filter to
298       *         {@code input}.
299       */
300      public Signal filter(Signal input)
301      {
302        Signal output = input.clone();
303        
304        FrequencyRange currRange = input.getCurrentRange();
305    
306        //If whole range cannot pass through filter, take intersection.
307        //If whole range does pass, we just return clone of input.
308        if (!passBand.contains(currRange))
309        {
310          FrequencyRange newRange  = currRange.clone().intersectWith(passBand);
311    
312          //If there is NO intersection, the entire signal is blocked and will
313          //be made null-like.
314          if (newRange.getWidth().getValue().signum() == 0)
315          {
316            output.nullify();
317          }
318          //If there is some intersection, create new proxied range by mapping
319          //low and high frequencies of new current range.
320          else
321          {
322            FrequencyRange newProxiedRange = new FrequencyRange
323            (
324              input.getProxiedFrequencyFor(newRange.getLowFrequency()),
325              input.getProxiedFrequencyFor(newRange.getHighFrequency())
326            );
327    
328            output.transform(newRange, newProxiedRange, false);
329          }  
330        }
331        
332        return output;
333      }
334    }