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    }