001    package edu.nrao.sss.electronics;
002    
003    import java.math.BigDecimal;
004    
005    import edu.nrao.sss.measure.Frequency;
006    import edu.nrao.sss.measure.FrequencyRange;
007    
008    /**
009     * A signal path that begins with a local oscillator and contains
010     * an optional multiplier and optional output filter.
011     * <p>
012     * <b>Version Info:</b>
013     * <table style="margin-left:2em">
014     *   <tr><td>$Revision: 1693 $</td></tr>
015     *   <tr><td>$Date: 2008-11-06 11:56:39 -0700 (Thu, 06 Nov 2008) $</td></tr>
016     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
017     * </table></p>
018     * 
019     * @author David M. Harland
020     * @since 2007-11-07
021     */
022    public class LocalOscillatorPath  //TODO method comments for whole class
023      implements SignalSource
024    {
025      private SignalSource     signalSource;
026      private LocalOscillator  oscillator;
027      private SignalMultiplier multiplier;
028      private SignalFilter     filter;
029      
030      /**
031       * Creates a new local oscillator path.
032       * 
033       * @param localOsc
034       *   this path's source of signal.  This is usually a
035       *   {@link LocalOscillator}, but does not have to be.
036       *   A value of <i>null</i> is permitted, but will render this
037       *   object nearly useless, as most of its methods will return
038       *   <i>null</i>.
039       *   
040       * @param loMultiplier
041       *   the amount by which the signal from {@code localOsc} should
042       *   be multiplied.  A value of <i>null</i> indicates that no
043       *   multiplication should occur.
044       *   
045       * @param outputFilter
046       *   the filter through which the multiplied signal (if any
047       *   multiplication occurs) is passed.  A value of <i>null</i>
048       *   indicates that no filtering should occur.
049       */
050      public LocalOscillatorPath(SignalSource   localOsc,
051                                 BigDecimal     loMultiplier,
052                                 FrequencyRange outputFilter)
053      {
054        //TODO err if loMult <= 0.0
055        
056        signalSource = localOsc;  //OK if null
057        
058        oscillator =
059          (localOsc instanceof LocalOscillator) ? (LocalOscillator)signalSource
060                                                : null;
061        multiplier =
062          loMultiplier == null ? null : new SignalMultiplier(null, loMultiplier);
063        
064        filter = 
065          outputFilter == null ? null : SignalFilter.make(null, outputFilter);
066      }
067    
068      /**
069       * Creates a new local oscillator path.
070       * 
071       * @param localOsc
072       *   this path's source of signal.  This is usually a
073       *   {@link LocalOscillator}, but does not have to be.
074       *   A value of <i>null</i> is permitted, but will render this
075       *   object nearly useless, as most of its methods will return
076       *   <i>null</i>.
077       *   
078       * @param loMultiplier
079       *   the amount by which the signal from {@code localOsc} should
080       *   be multiplied.
081       *   
082       * @param outputFilter
083       *   the filter through which the multiplied signal (if any
084       *   multiplication occurs) is passed.  A value of <i>null</i>
085       *   indicates that no filtering should occur.
086       */
087      public LocalOscillatorPath(SignalSource   localOsc,
088                                 int            loMultiplier,
089                                 FrequencyRange outputFilter)
090      {
091        this(localOsc, new BigDecimal(loMultiplier), outputFilter);
092      }
093      
094      /**
095       * Returns the value to which this path's source of signal should be
096       * tuned in order to move one frequency to another.  This method
097       * takes into account the multiplier and filter on this path.
098       * It attempts to reach {@code to} by subtracting {@code from} from
099       * its multiplied signal.
100       * <p><i>
101       * (TODO: not yet handling filter.  What if desired tuning doesn't
102       * pass through filter?  Exception?  Return null?  Return 0.0Hz?)</i></p>
103       * <p><i>
104       * (TODO: same issue if subtraction can not accomplish goal.)</i></p>
105       * 
106       * @param from
107       *   the frequency to be shifted.
108       *   
109       * @param to
110       *   the result of shifting {@code from} using this path's LO
111       *   and multiplier.
112       *   
113       * @return the frequency to which this path's source of signal (usually
114       *         a local oscillator) should be tuned in order to convert
115       *         {@code from} to {@code to}.
116       */
117      public Frequency getLOFreqToMoveCenterViaSubtraction(Frequency from,
118                                                           Frequency to)
119      {
120        //At this point we are calculating the path output
121        Frequency loTuning = to.clone().add(from);
122        
123        //Can pathOutput pass through filter?
124        //TODO handle outside-of-filter condition
125        
126        //Now we turn path output into LO tuning
127        loTuning.divideBy(multiplier.getMultiplicationFactor());
128        
129        return loTuning;
130      }
131      
132      
133      /**
134       * Returns the value to which this path's source of signal should be
135       * tuned in order to move one frequency to another.  This method
136       * takes into account the multiplier and filter on this path.
137       * It attempts to reach {@code to} by adding its multiplied signal
138       * to {@code from}.
139       * <p><i>
140       * (TODO: not yet handling filter.  What if desired tuning doesn't
141       * pass through filter?  Exception?  Return null?  Return 0.0Hz?)</i></p>
142       * <p><i>
143       * (TODO: same issue if addition can not accomplish goal.)</i></p>
144       * 
145       * @param from
146       *   the frequency to be shifted.
147       *   
148       * @param to
149       *   the result of shifting {@code from} using this path's LO
150       *   and multiplier.
151       *   
152       * @return the frequency to which this path's source of signal (usually
153       *         a local oscillator) should be tuned in order to convert
154       *         {@code from} to {@code to}.
155       */
156      public Frequency getLOFreqToMoveCenterViaAddition(Frequency from,
157                                                        Frequency to)
158      {
159        //At this point we are calculating the path output
160        Frequency loTuning = to.clone().subtract(from);
161        
162        //Can pathOutput pass through filter?
163        //TODO handle outside-of-filter condition
164        
165        //Now we turn path output into LO tuning
166        loTuning.divideBy(multiplier.getMultiplicationFactor());
167        
168        return loTuning;
169      }
170      
171      /**
172       * Sets this path's local oscillator to a frequency that will convert
173       * {@code from} to {@code to} via subtraction.
174       * <p>
175       * This method accomplishes its task by first calling
176       * {@link #getLOFreqToMoveCenterViaSubtraction(Frequency, Frequency)},
177       * so all the caveats for that method apply here.
178       * In addition, if this path's source of signal is not an LO,
179       * no tuning can be performed.</p>
180       * 
181       * @param from
182       *   the frequency to be shifted.
183       *   
184       * @param to
185       *   the result of shifting {@code from} using this path's LO
186       *   and multiplier.
187       */
188      public void setLOToMoveCenterViaSubtraction(Frequency from, Frequency to)
189      {
190        if (oscillator != null)
191          oscillator.tuneTo(getLOFreqToMoveCenterViaSubtraction(from, to));
192      }
193      
194      /**
195       * Sets this path's local oscillator to a frequency that will convert
196       * {@code from} to {@code to} via addition.
197       * <p>
198       * This method accomplishes its task by first calling
199       * {@link #getLOFreqToMoveCenterViaAddition(Frequency, Frequency)},
200       * so all the caveats for that method apply here.
201       * In addition, if this path's source of signal is not an LO,
202       * no tuning can be performed.</p>
203       * 
204       * @param from
205       *   the frequency to be shifted.
206       *   
207       * @param to
208       *   the result of shifting {@code from} using this path's LO
209       *   and multiplier.
210       */
211      public void setLOToMoveCenterViaAddition(Frequency from, Frequency to)
212      {
213        if (oscillator != null)
214          oscillator.tuneTo(getLOFreqToMoveCenterViaAddition(from, to));
215      }
216      
217      /**
218       * Returns the signal produced by this local oscillator path.
219       * This is normally a narrow-band signal that was produced by an LO,
220       * optionally multiplied, and optionally filtered.
221       * <p>
222       * The returned value will be <i>null</i> if this path has no source,
223       * or if its source produces a <i>null</i> signal.</p>
224       * 
225       * @return the signal produced by this local oscillator path.
226       */
227      public Signal getSignal()
228      {
229        Signal signal = null;
230        
231        if (signalSource != null)
232        {
233          signal = signalSource.getSignal();
234        
235          if (signal != null)
236          {
237            if (multiplier != null)
238              signal = multiplier.multiply(signal);
239        
240            if (filter != null)
241              signal = filter.filter(signal);
242          }
243        }
244        
245        return signal;
246      }
247      
248      /**
249       * Returns the multiplication factor used on the local oscillator's signal.
250       * 
251       * @return the multiplier used by this LO path.
252       * 
253       * @since 2008-11-06
254       */
255      public BigDecimal getMultiplier()
256      {
257        return multiplier == null ? BigDecimal.ONE : multiplier.getMultiplicationFactor();
258      }
259      
260      /**
261       * Returns the broadest possible range the signal from for this LO path.
262       * Note that the LO attached to this path might not actually be able
263       * to achieve the lower or upper most values if it has discrete
264       * tuning steps.
265       * 
266       * @return  the broadest possible range the signal from for this LO path.
267       * 
268       * @since 2008-11-05
269       */
270      public FrequencyRange getOutputRange()
271      {
272        //Infinite range to start
273        FrequencyRange output = new FrequencyRange();
274    
275        //If we have an LO, use its range
276        if (oscillator != null)
277          output = oscillator.getTunableRange();
278    
279        //Use multiplier, if we have one
280        if (multiplier != null && !output.getWidth().isInfinite())
281          output.multiplyBy(multiplier.getMultiplicationFactor());
282            
283        //If there is a filter, intersect it with the current range.
284        //If there is no overlap of these ranges, the output will have zero width.
285        if (filter != null)
286          output.intersectWith(filter.getPassBand());
287          
288        return output;
289      }
290      
291      //============================================================================
292      // 
293      //============================================================================
294      /*
295      public static void main(String[] args) throws Exception
296      {
297        Frequency low  = new Frequency(11904.0, edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ);
298        Frequency high = new Frequency(20352.0, edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ);
299        Frequency step = new Frequency(  256.0, edu.nrao.sss.measure.FrequencyUnits.MEGAHERTZ);
300        
301        LocalOscillator locOsc = new LocalOscillator(new FrequencyRange(low,high),step);
302        
303        FrequencyRange band = new FrequencyRange(new Frequency(18.0), new Frequency(26.0));
304        
305        Frequency newCenter = new Frequency(10.0);
306    
307        LocalOscillatorPath loPath = new LocalOscillatorPath(locOsc, 2.0, null);
308        
309        loPath.setLOToMoveCenterViaSubtraction(band.getCenterFrequency(), newCenter);
310    
311        System.out.println("LO is tuned to " + locOsc.getCurrentTuning()+"\n");
312    
313        Signal signal = new Signal(band, edu.nrao.sss.astronomy.PolarizationType.L);
314        System.out.println("INPUT");
315        System.out.println(signal);
316        SignalMixer mixer = SignalMixer.makeSubtractiveMixer();
317        mixer.getLocalOscillatorInputPipe().connectInputTo(loPath);
318        signal = mixer.mix(signal);
319        System.out.println("MIXED OUTPUT");
320        System.out.println(signal);
321        
322        System.out.println("CENTERS");
323        System.out.println("  Desired: " + newCenter);
324        System.out.println("  Actual:  " + signal.getCurrentRange().getCenterFrequency());
325      }
326      */
327    }