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    import edu.nrao.sss.measure.FrequencyUnits;
008    
009    /**
010     * A frequency generator.
011     * <p>
012     * The frequency generated by this device is usually mixed with a
013     * signal in order to move that signal to an intermediate frequency.</p>
014     * <p>
015     * <b>Version Info:</b>
016     * <table style="margin-left:2em">
017     *   <tr><td>$Revision: 1707 $</td></tr>
018     *   <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr>
019     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
020     * </table></p>
021     * 
022     * @author David M. Harland
023     * @since 2007-10-24
024     */
025    public class LocalOscillator
026      implements Cloneable, SignalSource
027    {
028      private static final String DEFAULT_NAME = "[Unnamed LO]";
029      
030      private String name;
031    
032      private boolean        isContinuous;
033      private FrequencyRange tunableRange;
034      private Frequency      stepSize;
035      private Frequency      currentTuning;
036      
037      //Used only to speed up discrete tuning logic
038      private FrequencyUnits discreteUnits;
039      private BigDecimal     minFreq;
040    //private BigDecimal     maxFreq;
041      private BigDecimal     tuningIncr;
042      
043      //TODO should an LO have an on/off property?
044      
045      /**
046       * Creates a continuously tunable oscillator whose frequencies are
047       * limited to the given range.
048       * <p>
049       * This oscillator will be initially tuned to its lowest frequency.</p>
050       * 
051       * @param tunableRange
052       *   the minimum and maximum frequencies to which this oscillator
053       *   may be tuned.
054       *   If this value is <i>null</i> a {@link NullPointerException}
055       *   will be thrown.
056       */
057      public LocalOscillator(FrequencyRange tunableRange)
058      {
059        this.name          = DEFAULT_NAME;
060        this.tunableRange  = tunableRange.clone();
061        this.stepSize      = new Frequency("0.0", FrequencyUnits.HERTZ);
062        this.isContinuous  = true;
063        this.currentTuning = tunableRange.getLowFrequency();
064      }
065      
066      /**
067       * Creates an oscillator whose tunings are limited to the given range
068       * and whose "dial" moves in increments of {@code tuningStepSize}.
069       * <p>
070       * The minimum frequency to which this oscillator may be tuned is
071       * {@code tunableRange.getLowFrequency()}.  The next lowest frequency is
072       * {@code tunableRange.getLowFrequency() + tuningStepSize}.  The highest
073       * frequency is given by the largest value <tt>N</tt> such that
074       * {@code tunableRange.getLowFrequency() + tuningStepSize * N <=
075       * tunableRange.getHighFrequency()}.  That is, the greatest frequency
076       * to which this oscillator may be tuned might be lower than the high
077       * frequency of {@code tunableRange}.</p>
078       * <p>
079       * This oscillator will be initially tuned to its lowest frequency.</p>
080       * 
081       * @param tunableRange
082       *   the minimum and maximum frequencies to which this oscillator
083       *   may be tuned.
084       *   If this value is <i>null</i> a {@link NullPointerException}
085       *   will be thrown.
086       * 
087       * @param tuningStepSize
088       *   the distance, in frequency units, between two adjacent tunings of
089       *   this oscillator.
090       *   If this value is <i>null</i> a {@link NullPointerException}
091       *   will be thrown.
092       */
093      public LocalOscillator(FrequencyRange tunableRange, Frequency tuningStepSize)
094      {
095        this.name          = DEFAULT_NAME;
096        this.tunableRange  = tunableRange.clone();
097        this.stepSize      = tuningStepSize.clone();
098        this.isContinuous  = false;
099        this.currentTuning = tunableRange.getLowFrequency();
100    
101        prepForDiscreteTuning();
102      }
103    
104      //============================================================================
105      // TUNING
106      //============================================================================
107    
108      /** 
109       * Adjusts high frequency of tunable range so that it is
110       * a tunable frequency.
111       */
112      private void prepForDiscreteTuning()
113      {
114        //We may run into fewer decimal-in-binary problems if range
115        //and step size are in same units.
116        FrequencyUnits units = stepSize.getUnits();
117        tunableRange.convertTo(units);
118        Frequency lowFreq = tunableRange.getLowFrequency();
119        
120        //Mimic modulo operator
121        BigDecimal divisor  = stepSize.getValue();
122        BigDecimal dividend = tunableRange.getWidth().toUnits(units);
123        BigDecimal quotient = dividend.divideToIntegralValue(divisor);
124        BigDecimal high     = divisor.multiply(quotient).add(lowFreq.toUnits(units));
125        
126        //Update the range with new, possibly lower, high frequency
127        tunableRange.set(lowFreq, new Frequency(high, units));
128        
129        //Cache some values
130        discreteUnits = units;
131        minFreq       = lowFreq.getValue();
132      //maxFreq       = high;
133        tuningIncr    = divisor;
134      }
135      
136      /**
137       * Returns <i>true</i> if this oscillator is continuously tunable.
138       * @return <i>true</i> if this oscillator is continuously tunable.
139       */
140      public boolean isContinuouslyTunable()
141      {
142        return isContinuous;
143      }
144    
145      /**
146       * Returns the distance between adjacent tunings of this oscillator.
147       * If this oscillator is continuously tunable, the returned
148       * step size will be zero.
149       * <p>
150       * The returned frequency is a copy of the one held internally by
151       * this object, so changes made to it after this call will
152       * <i>not</i> be reflected herein.</p>
153       * <p>
154       * The returned frequency will never be <i>null</i>.</p>
155       * 
156       * @return the distance between adjacent tunings of this oscillator.
157       */
158      public Frequency getStepSize()
159      {
160        return stepSize.clone();
161      }
162      
163      /**
164       * Returns the frequency range over which this oscillator may be tuned.
165       * <p>
166       * The returned range is a copy of the one held internally by
167       * this object, so changes made to it after this call will
168       * <i>not</i> be reflected herein.</p>
169       * <p>
170       * The returned range will never be <i>null</i>.</p>
171       * 
172       * @return the frequency range over which this oscillator may be tuned.
173       */
174      public FrequencyRange getTunableRange()
175      {
176        return tunableRange.clone();
177      }
178      
179      /**
180       * Returns the frequency to which this oscillator is currently tuned.
181       * @return the frequency to which this oscillator is currently tuned.
182       */
183      public Frequency getCurrentTuning()
184      {
185        return currentTuning.clone();
186      }
187      
188      /**
189       * Attempts to change the output frequency of this oscillator to
190       * {@code desiredFrequency}.
191       * <p>
192       * An attempt to tune to a frequency below this oscillator's minimum
193       * will result in a tuning equal to the lowest allowable frequency,
194       * and likewise for exceeding this oscillator's maximum.</p>
195       * <p>
196       * If this oscillator is not continuously tunable, then this method
197       * will tune to the allowable frequency that is closest to
198       * {@code desiredFrequency}.  If the {@code desiredFrequency} is
199       * not a tunable frequency and is
200       * equally distant from two allowable tuning frequencies, this
201       * oscillator will be tuned to the lower of those frequencies.</p>
202       * 
203       * @return the frequency to which this oscillator was tuned as a result
204       *         of this call.
205       */
206      public Frequency tuneTo(Frequency desiredFrequency)
207      {
208        //If desired is <= lowest, use lowest
209        Frequency temp = tunableRange.getLowFrequency();
210        if (desiredFrequency.compareTo(temp) <= 0)
211        {
212          currentTuning = temp;
213        }
214        else
215        {
216          //If desired >= highest, use highest
217          temp = tunableRange.getHighFrequency();
218          if (desiredFrequency.compareTo(temp) >= 0)
219          {
220            currentTuning = temp;
221          }
222          else //desired is in tunable range
223          {
224            setTuning(desiredFrequency);
225          }
226        }
227        
228        //Put the tuning into the same units as the desired frequency
229        currentTuning.convertTo(desiredFrequency.getUnits());
230        
231        return currentTuning.clone();
232      }
233    
234      //Use only when you already know f is in bounds.
235      private void setTuning(Frequency f)
236      {
237        currentTuning = isContinuous ? f.clone() : getClosest(f);
238      }
239      
240      /**
241       * Tunes this local oscillator as closely as possible to the center
242       * of its range.
243       * <p>
244       * It may not be possible to tune a discretely tunable LO to the center of
245       * its range.  In that situation the closest legal tuning will be chosen.
246       * If there are two such tunings, the one chosen will be determined as
247       * outlined in {@link #tuneTo(Frequency)}.</p>
248       * 
249       * @return
250       *   the frequency to which this local oscillator is now tuned.
251       */
252      public Frequency tuneToCenter()
253      {
254        setTuning(tunableRange.getCenterFrequency());
255        
256        return currentTuning;
257      }
258      
259      /**
260       * Returns <i>true</i> if this oscillator can be tuned to the given frequency.
261       * 
262       * @param potentialTuning
263       *   the frequency to be tested.
264       *   
265       * @return
266       *    <i>true</i> if this oscillator can be tuned to {@code potentialTuning}.
267       *    
268       * @since 2008-10-30
269       */
270      public boolean isTunableTo(Frequency potentialTuning)
271      {
272        Frequency closest = getClosestTunableFrequency(potentialTuning);
273        
274        return closest.equals(potentialTuning);
275      }
276      
277      /**
278       * Returns the legal tuning that is closed to {@code potentialTuning}.
279       * If two frequencies are equally close, the lower is returned.
280       * 
281       * @param potentialTuning
282       *   a frequency to which this oscillator might be tuned.
283       *   
284       * @return
285       *   the legal tuning that is closed to {@code potentialTuning}.
286       *    
287       * @since 2008-10-30
288       */
289      public Frequency getClosestTunableFrequency(Frequency potentialTuning)
290      {
291        //Some quick exits
292        Frequency min = tunableRange.getLowFrequency();
293        if (potentialTuning.compareTo(min) <= 0)
294          return min;
295    
296        Frequency max = tunableRange.getHighFrequency();
297        if (potentialTuning.compareTo(max) >= 0)
298          return max;
299        
300        //By this point we know we're in range
301        if (isContinuous)
302          return potentialTuning.clone();
303        
304        //By this point we know we're in range and tuning is discrete
305        return getClosest(potentialTuning);
306      }
307      
308      //Used by methods that know tuning is discrete and f is in range.
309      private Frequency getClosest(Frequency f)
310      {
311        //We look first for two consecutive multiples of the tuning increment
312        //that bracket the desired frequency.  These are
313        //n * tuningIncr &&  (n+1) * tuningIncr.
314        //We then see which legal tuning is closest to the desired frequency.
315        //If both are equally close, we choose the lower.
316        BigDecimal target   = f.toUnits(discreteUnits);
317        BigDecimal n        = target.subtract(minFreq).divideToIntegralValue(tuningIncr);
318        BigDecimal lower    = tuningIncr.multiply(n).add(minFreq);
319        BigDecimal higher   = lower.add(tuningIncr);
320        BigDecimal diffLow  = target.subtract(lower);
321        BigDecimal diffHigh = higher.subtract(target);
322    
323        BigDecimal result = diffLow.compareTo(diffHigh) <= 0 ? lower : higher;
324        
325        return new Frequency(result, discreteUnits);
326      }
327      
328      //============================================================================
329      // NAMING
330      //============================================================================
331    
332      /**
333       * Sets a new name for this oscillator.
334       * Clients are not required to name their oscillator.
335       * <p>
336       * If {@code newName} is <i>null</i> or the empty string
337       * (<tt>""</tt>), the request to change the name will be
338       * denied and the current name will remain in place.</p>
339       * 
340       * @param newName
341       *   the new name of this oscillator.
342       */
343      public void setName(String newName)
344      {
345        if (newName != name && newName.length() > 0)
346          name = newName;
347      }
348      
349      /**
350       * Returns the name of this oscillator.
351       * The returned value will be either a client-specified name or a default
352       * name.  It will never be <i>null</i>.
353       * 
354       * @return the name of this oscillator.
355       */
356      public String getName()
357      {
358        return name;
359      }
360    
361      //============================================================================
362      // INTERFACE SignalSource
363      //============================================================================
364      
365      /**
366       * Returns a signal holding the frequency to which this oscillator is
367       * currently tuned.
368       */
369      public Signal getSignal()
370      {
371        return new Signal(currentTuning);
372      }
373    
374      //============================================================================
375      // 
376      //============================================================================
377      
378      /**
379       * Creates and returns a copy of this oscillator.
380       * <p>
381       * If anything goes wrong during the cloning procedure,
382       * a {@link RuntimeException} will be thrown.</p>
383       */
384      @Override
385      public LocalOscillator clone()
386      {
387        LocalOscillator clone = null;
388        
389        try
390        {
391          clone = (LocalOscillator)super.clone();
392          
393          clone.tunableRange  = this.tunableRange.clone();
394          clone.stepSize      = this.stepSize.clone();
395          clone.currentTuning = this.currentTuning.clone();
396        }
397        catch (Exception ex)
398        {
399          throw new RuntimeException(ex);
400        }
401        
402        return clone;
403      }
404      
405      /** Returns <i>true</i> if {@code o} is equal to this local oscillator. */
406      @Override
407      public boolean equals(Object o)
408      {
409        //Quick exit if o is this
410        if (o == this)
411          return true;
412        
413        //Quick exit if o is null
414        if (o == null)
415          return false;
416       
417        //Quick exit if classes are different
418        if (!o.getClass().equals(this.getClass()))
419          return false;
420        
421        //A safe cast if we got this far
422        LocalOscillator other = (LocalOscillator)o;
423    
424        //Intentionally not comparing current tuning
425        return
426          other.isContinuous == this.isContinuous &&
427          other.name.equals(this.name) &&
428          other.stepSize.equals(this.stepSize) &&
429          other.tunableRange.equals(this.tunableRange);
430      }
431    
432      /** Returns a hash code value for this signal. */
433      @Override
434      public int hashCode()
435      {
436        //Taken from the Effective Java book by Joshua Bloch.
437        //The constants 17 & 37 are arbitrary & carry no meaning.
438        int result = 17;
439        
440        //You MUST keep this method in sync w/ the equals method
441        
442        result = 37 * result + (isContinuous ? Boolean.TRUE.hashCode()
443                                              : Boolean.FALSE.hashCode());
444        result = 37 * result + name.hashCode();
445        result = 37 * result + stepSize.hashCode();
446        result = 37 * result + tunableRange.hashCode();
447        
448        return result;
449      }
450    }