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 }