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 device that mixes a {@link Signal signal} with another frequency. 011 * The output of this mixing is a third signal that represents the 012 * first, but that has been transformed to an intermediate frequency. 013 * <p> 014 * In contrast to a true signal mixer, this object produces a single 015 * signal that contains either the sum or difference of the input 016 * signals and the mixing signal. A true mixer produces both the 017 * sum and difference simultaneously.</p> 018 * <p> 019 * <b>Version Info:</b> 020 * <table style="margin-left:2em"> 021 * <tr><td>$Revision: 1204 $</td></tr> 022 * <tr><td>$Date: 2008-04-07 16:53:41 -0600 (Mon, 07 Apr 2008) $</td></tr> 023 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 024 * </table></p> 025 * 026 * @author David M. Harland 027 * @since 2007-10-24 028 */ 029 public class SignalMixer 030 implements SignalProcessor, SignalSource 031 { 032 private static final String DEFAULT_NAME = "[unnamed-mixer]"; 033 034 private String name; 035 036 private Signal inputSignal; 037 private Signal outputSignal; 038 039 private SignalPipe inputPipe; 040 private SignalPipe outputPipe; 041 042 private SignalProcessorSink loSink; 043 private Frequency loFreq; //from most recent mixing 044 045 private boolean performingAnUpdateNow; 046 047 //This is set at construction time -- never change it. 048 private boolean useSubtraction; 049 050 //============================================================================ 051 // OBJECT CREATION 052 //============================================================================ 053 054 /** 055 * Creates a new signal mixer. 056 */ 057 private SignalMixer(String deviceName) 058 { 059 name = deviceName; 060 inputSignal = null; 061 outputSignal = null; 062 inputPipe = SignalPipe.makeAndWeldOutputTo(this); 063 outputPipe = SignalPipe.makeAndWeldInputTo(this); 064 loSink = new SignalProcessorSink(); 065 } 066 067 /** 068 * Creates and returns a new mixer that produces an output signal by 069 * adding an input signal to a local oscillator signal. 070 * 071 * @param deviceName 072 * an optional name for this mixer. If this value is <i>null</i> 073 * a default name will be used in its place. 074 * 075 * @return a new mixer that uses addition to produce its output. 076 */ 077 public static SignalMixer makeAdditiveMixer(String deviceName) 078 { 079 SignalMixer newMixer = new SignalMixer(deviceName); 080 081 newMixer.useSubtraction = false; 082 083 return newMixer; 084 } 085 086 /** 087 * Creates and returns a new mixer that produces an output signal by 088 * subtracting an input signal from a local oscillator signal. 089 * 090 * @param deviceName 091 * an optional name for this mixer. If this value is <i>null</i> 092 * a default name will be used in its place. 093 * 094 * @return a new mixer that uses subtraction to produce its output. 095 */ 096 public static SignalMixer makeSubtractiveMixer(String deviceName) 097 { 098 SignalMixer newMixer = new SignalMixer(deviceName); 099 100 newMixer.useSubtraction = true; 101 102 return newMixer; 103 } 104 105 //============================================================================ 106 // 107 //============================================================================ 108 109 /** 110 * Returns the pipe that carries a local oscillator's signal into this mixer. 111 * A typical usage pattern for this method is:<pre> 112 * myMixer.getLocalOscillatorInputPipe().connectInputTo(myOscillator);</pre> 113 * <p> 114 * Note that any {@link SignalSource}, not just a {@link LocalOscillator}, 115 * can be connected to input of the returned pipe.</p> 116 * 117 * @return the pipe that carries a local oscillator's signal into this mixer. 118 */ 119 public SignalPipe getLocalOscillatorInputPipe() 120 { 121 return loSink.getInputPipe(); 122 } 123 124 /** 125 * Returns the input pipe of this device. 126 * A typical usage pattern for this method is:<pre> 127 * myMixer.getInputPipe().connectInputTo(mySource);</pre> 128 * 129 * @return the input pipe of this device. 130 */ 131 public SignalPipe getInputPipe() 132 { 133 return inputPipe; 134 } 135 136 /** 137 * Returns the output pipe of this device. 138 * A typical usage pattern for this method is:<pre> 139 * myMixer.getOutputPipe().connectOutputTo(myProcessor);</pre> 140 * 141 * @return the output pipe of this device. 142 */ 143 public SignalPipe getOutputPipe() 144 { 145 return outputPipe; 146 } 147 148 /** 149 * Returns a copy of the input signal most recently {@link #execute() 150 * processed} by this device. 151 * If this processor has never been executed, or if the input signal 152 * it processed most recently was <i>null</i>, the returned value will 153 * be <i>null</i>. 154 * 155 * @return the input signal most recently sent into this mixer. 156 */ 157 public Signal getMostRecentInput() 158 { 159 return inputSignal == null ? null : inputSignal.clone(); 160 } 161 162 /** 163 * Returns a copy of the output signal most recently {@link #execute() 164 * produced} by this device. 165 * If this processor has never been executed, or if the input signal 166 * it processed most recently was <i>null</i>, the returned value will 167 * be <i>null</i>. 168 * 169 * @return the output signal most recently produced by this mixer. 170 */ 171 public Signal getMostRecentOutput() 172 { 173 return outputSignal == null ? null : outputSignal.clone(); 174 } 175 176 /** 177 * Returns the name of this mixer. 178 * The returned name is either a client-specified name or a default name, 179 * but will never be <i>null</i>. 180 * 181 * @return the name of this mixer. 182 */ 183 public String getName() 184 { 185 if (name != null) 186 return name; 187 188 return loFreq == null ? DEFAULT_NAME : "mixer-" + loFreq.toString(); 189 } 190 191 /** 192 * Erases this device's memory of its most recent execution. 193 * @see #getMostRecentInput() 194 * @see #getMostRecentOutput() 195 * @see #getSignal() 196 */ 197 public void eraseSignalMemory() 198 { 199 inputSignal = null; 200 outputSignal = null; 201 } 202 203 //============================================================================ 204 // INTERFACE SignalSource 205 //============================================================================ 206 207 /** 208 * Returns a copy of the signal produced by the most recent {@link #execute() 209 * execution} of this device. 210 * If this device has never been run, or if the input signal 211 * it processed most recently was <i>null</i>, the returned value will 212 * be <i>null</i>. 213 */ 214 public Signal getSignal() 215 { 216 return outputSignal == null ? null : outputSignal.clone(); 217 } 218 219 //============================================================================ 220 // INTERFACE SignalProcessor AND SUPPORT THEREOF 221 //============================================================================ 222 223 /** 224 * Runs this device on the signal received from its input pipe and 225 * then executes the processor connected to its output pipe, if any. 226 * Both the input signal and the output produced by mixing it are 227 * remembered by this mixer and are available via the 228 * {@link #getMostRecentInput()} and {@link #getMostRecentOutput()} methods. 229 */ 230 public void execute() 231 { 232 updateOutputSignal(); 233 234 outputPipe.execute(); 235 } 236 237 public void executeUpTo(SignalProcessor firstUnexecutedDevice) 238 { 239 //Quick exit if execution should not occur 240 if (firstUnexecutedDevice == this) 241 return; 242 243 updateOutputSignal(); 244 245 //Tell text element of chain to do its work 246 outputPipe.executeUpTo(firstUnexecutedDevice); 247 } 248 249 private void updateOutputSignal() 250 { 251 //Do the work and remember results 252 inputSignal = inputPipe.getSignal(); 253 254 if (inputSignal != null) 255 { 256 performingAnUpdateNow = true; 257 258 outputSignal = mix(inputSignal); 259 260 performingAnUpdateNow = false; 261 262 String deviceName = getName(); 263 if (!outputSignal.getDevicePath().endsWith(deviceName)) 264 outputSignal.appendToDevicePath(deviceName); 265 } 266 else 267 { 268 outputSignal = null; 269 } 270 } 271 272 public void executeFromStartOfChainUpTo(SignalProcessor firstUnexecutedDevice) 273 { 274 inputPipe.executeFromStartOfChainUpTo(firstUnexecutedDevice); 275 } 276 277 /** 278 * Creates and returns a new signal that is the result of applying this 279 * mixer to the given input signal. 280 * <p> 281 * Note that the {@code input} signal and this mixer are 282 * not affected by calls to this method. In other words this mixer 283 * will not remember the input signal sent in or the output 284 * signal produced. This is in contrast to a call to the 285 * {@link #execute()} methods. Note also that in order to ensure 286 * an up to date local oscillator signal, this method executes the 287 * devices chained to this mixer's LO pipe from the beginning of that 288 * chain.</p> 289 * 290 * @param input 291 * the signal to be mixed. 292 * 293 * @return a new signal that is the result of applying this mixer to 294 * {@code input}. 295 */ 296 public Signal mix(Signal input) 297 { 298 //QUICK EXIT if input is null (or null-like) 299 //TODO confirm that this is the right thing to do 300 if (input == null) 301 return null; 302 303 Signal output = null; 304 305 //Get the local oscillator signal 306 loSink.executeFromStartOfChainUpTo(this); 307 308 Signal loSignal = loSink.getMostRecentSignal(); 309 310 //QUICK EXIT if no signal from local oscillator 311 //TODO confirm that this is the right thing to do. 312 // Alternative might be to mix w/ LO of 0Hz. 313 if (loSignal == null) 314 return input.clone(); 315 316 FrequencyRange currRange = input.getCurrentRange(); 317 Frequency mixerFreq = loSignal.getCurrentRange().getCenterFrequency(); 318 319 if (performingAnUpdateNow) 320 loFreq = mixerFreq.clone(); 321 322 Frequency mix1 = null, 323 mix2 = null; 324 325 if (useSubtraction) 326 { 327 Frequency currHigh = currRange.getHighFrequency(); 328 329 if (mixerFreq.compareTo(currHigh) >= 0) //yields only positive freqs 330 { 331 mix1 = mixerFreq.clone().subtract(currRange.getLowFrequency()); 332 mix2 = mixerFreq.subtract(currHigh); 333 } 334 else //special logic to deal w/ reflection of negative freqs 335 { 336 output = handleMixingThatCreatesNegativeFrequencies(input, loSignal); 337 } 338 } 339 else //use addition 340 { 341 mix1 = mixerFreq.clone().add(currRange.getLowFrequency()); 342 mix2 = mixerFreq.add(currRange.getHighFrequency()); 343 } 344 345 //output is null unless we ran handleMixingThatCreatesNegativeFrequencies 346 if (output == null) 347 { 348 //Only the current frequency range is modified 349 output = input.clone().transform(new FrequencyRange(mix1, mix2), 350 input.getProxiedRange(), useSubtraction); 351 } 352 return output; 353 } 354 355 /** 356 * Complicated logic for case when mixing would give freqs < 0. 357 * Use ONLY from within mix(Signal) method because it assumes that 358 * certain things are true about param "input". 359 * This method is here only so that we don't have to look at the 360 * special logic when following the normal path through mix(Signal). 361 */ 362 //TODO run this by a knowledgeable person 363 private Signal 364 handleMixingThatCreatesNegativeFrequencies(Signal input, Signal loSignal) 365 { 366 boolean reversingTransformation = true; 367 368 FrequencyRange currRange = input.getCurrentRange(); 369 FrequencyRange proxiedRange = input.getProxiedRange(); 370 371 Frequency mixerFreq = loSignal.getCurrentRange().getCenterFrequency(); 372 FrequencyUnits mixerUnits = mixerFreq.getUnits(); 373 374 BigDecimal mixerValue = mixerFreq.getValue(); 375 BigDecimal lowValue = currRange.getLowFrequency().toUnits(mixerUnits); 376 BigDecimal highValue = currRange.getHighFrequency().toUnits(mixerUnits); 377 378 Frequency mix1, mix2; 379 380 //If LO freq is below input range, all freqs are negative. In this 381 //case we just turn the signs around and declare that we're really 382 //using addition. 383 if (mixerValue.compareTo(lowValue) <= 0) 384 { 385 mix1 = new Frequency(highValue.subtract(mixerValue), mixerUnits); 386 mix2 = new Frequency( lowValue.subtract(mixerValue), mixerUnits); 387 reversingTransformation = false; 388 } 389 //Otherwise, the LO freq is inside the input range, and this causes some 390 //headaches. We are taking the approach here that such a situation 391 //filters the range to a smaller range. 392 else 393 { 394 //Find out if LO is closer to the low or the high value of the input range 395 BigDecimal diffHigh = highValue.subtract(mixerValue); 396 BigDecimal diffLow = mixerValue.subtract(lowValue); 397 BigDecimal biggerDiff = 398 diffHigh.compareTo(diffLow) > 0 ? diffHigh : diffLow; 399 400 //The new range will be from zero to the greater of the two (positive) 401 //differences. One can think of the negative frequencies as being 402 //reflected from zero back into the positive part of the number line. 403 mix1 = new Frequency(BigDecimal.ZERO, mixerUnits); 404 mix2 = new Frequency(biggerDiff, mixerUnits); 405 406 Frequency newWidth = mix2.clone(); //since mix1 == 0 407 408 //Reset proxied range based on new min/max. Note that from frequency zero 409 //up to the smaller of the two diffs is a "confused" region where a 410 //particular current frequency is a proxy for two signal frequencies. 411 //Example: input signal has curr range of 8-13, representing freqs 412 //28-33. If we mix with LO of 11, we get range of 3-(-2), which 413 //we then say is range 3-0, now representing 28-31. Frequency 1 really 414 //represents both 30 & 32, and freq 2 reps 29 and 33. Only the range 415 //3-2, representing 28-29 is not "confused". 416 417 //The LO freq is closer to the high, than the low, of the input. 418 if (diffHigh.compareTo(diffLow) <= 0) 419 { 420 if (input.proxiedRangeIsReversed()) 421 { 422 Frequency pHigh = proxiedRange.getHighFrequency(); 423 proxiedRange.set(pHigh.clone().subtract(newWidth), pHigh); 424 } 425 else 426 { 427 Frequency pLow = proxiedRange.getLowFrequency(); 428 proxiedRange.set(pLow, pLow.clone().add(newWidth)); 429 } 430 431 //keep reversingTransformation == true here 432 } 433 //The LO freq is closer to the low, than the high, of the input. 434 else 435 { 436 if (input.proxiedRangeIsReversed()) 437 { 438 Frequency pLow = proxiedRange.getLowFrequency(); 439 proxiedRange.set(pLow, pLow.clone().add(newWidth)); 440 } 441 else 442 { 443 Frequency pHigh = proxiedRange.getHighFrequency(); 444 proxiedRange.set(pHigh.clone().subtract(newWidth), pHigh); 445 } 446 447 reversingTransformation = false; 448 } 449 } 450 451 return input.clone().transform(new FrequencyRange(mix1, mix2), 452 proxiedRange, reversingTransformation); 453 } 454 }