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 device that samples a signal to create a {@link DigitalSignal}.
010     * <p>
011     * <b>Version Info:</b>
012     * <table style="margin-left:2em">
013     *   <tr><td>$Revision: 1269 $</td></tr>
014     *   <tr><td>$Date: 2008-05-05 14:39:39 -0600 (Mon, 05 May 2008) $</td></tr>
015     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
016     * </table></p>
017     * 
018     * @author David M. Harland
019     * @since 2007-11-02
020     */
021    public class SignalSampler
022      implements SignalProcessor, SignalSource
023    {
024      private String name;
025    
026      private int       bitsPerSample;
027      private Frequency samplingRate;
028      //TODO should there be an acceptable-input-range variable?
029      
030      private SignalMixer virtualMixer;
031      
032      private Signal        inputSignal;
033      private DigitalSignal outputSignal;
034      
035      private SignalPipe inputPipe;
036      private SignalPipe outputPipe;
037    
038      /**
039       * Creates a new sampler that produces signals with the given properties.
040       * 
041       * @param deviceName
042       *   an optional name for this sampler.  If this value is <i>null</i>
043       *   a default name will be used in its place.
044       * 
045       * @param numberOfBitsPerSample
046       *   the number of bits in one sample of a digital signal produced by
047       *   this device.
048       *   
049       * @param samplesPerSecond
050       *   the number of samples of a signal taken per second by this device.
051       */
052      public SignalSampler(String deviceName,
053                           int numberOfBitsPerSample, Frequency samplesPerSecond)
054      {
055        name = (deviceName == null) ?
056               numberOfBitsPerSample + "-bit-at-" + samplesPerSecond : deviceName;
057        
058        bitsPerSample = numberOfBitsPerSample;
059        samplingRate  = samplesPerSecond.clone();
060        virtualMixer  = SignalMixer.makeSubtractiveMixer(null);
061        inputSignal   = null;
062        outputSignal  = null;
063        inputPipe     = SignalPipe.makeAndWeldOutputTo(this);
064        outputPipe    = SignalPipe.makeAndWeldInputTo(this);
065        
066        resetMixerLO();
067      }
068      
069      /** Call this whenever samplingRate changes. */
070      private void resetMixerLO()
071      {
072        virtualMixer.getLocalOscillatorInputPipe().connectInputTo
073        (
074          new SignalSource()
075          {
076            public Signal getSignal()
077            {
078              return new Signal(samplingRate);
079            }
080          }
081        );
082      }
083      
084      /**
085       * Sets the name of this device.
086       * <p>
087       * If {@code newName} is <i>null</i> or the empty string
088       * (<tt>""</tt>), the request to change the name will be
089       * denied and the current name will remain in place.</p>
090       * 
091       * @param newName
092       *   an optional name for this sampler.
093       */
094      public void setName(String newName)
095      {
096        if (newName != name && newName.length() > 0)
097          name = newName;
098      }
099      
100      /**
101       * Returns the name of this device.
102       * @return the name of this device.
103       */
104      public String getName()
105      {
106        return name;
107      }
108      
109      /**
110       * Returns the input pipe of this device.
111       * A typical usage pattern for this method is:<pre>
112       * mySampler.getInputPipe().connectInputTo(mySource);</pre>
113       * 
114       * @return the input pipe of this device.
115       */
116      public SignalPipe getInputPipe()
117      {
118        return inputPipe;
119      }
120      
121      /**
122       * Returns the output pipe of this device.
123       * A typical usage pattern for this method is:<pre>
124       * mySampler.getOutputPipe().connectOutputTo(myProcessor);</pre>
125       * 
126       * @return the output pipe of this device.
127       */
128      public SignalPipe getOutputPipe()
129      {
130        return outputPipe;
131      }
132      
133      /**
134       * Returns a copy of the input signal most recently {@link #execute()
135       * processed} by this device.
136       * If this processor has never been executed, or if the input signal
137       * it processed most recently was <i>null</i>, the returned value will
138       * be <i>null</i>.
139       * 
140       * @return the input signal most recently sent into this sampler.
141       */
142      public Signal getMostRecentInput()
143      {
144        return inputSignal == null ? null : inputSignal.clone();
145      }
146      
147      /**
148       * Returns a copy of the output signal most recently {@link #execute()
149       * produced} by this device.
150       * If this processor has never been executed, or if the input signal
151       * it processed most recently was <i>null</i>, the returned value will
152       * be <i>null</i>.
153       * 
154       * @return the output signal most recently produced by this sampler.
155       */
156      public DigitalSignal getMostRecentOutput()
157      {
158        return outputSignal == null ? null : outputSignal.clone();
159      }
160    
161      /**
162       * Erases this device's memory of its most recent execution.
163       * @see #getMostRecentInput()
164       * @see #getMostRecentOutput()
165       * @see #getSignal()
166       */
167      public void eraseSignalMemory()
168      {
169        inputSignal  = null;
170        outputSignal = null;
171      }
172    
173      //============================================================================
174      // INTERFACE SignalSource
175      //============================================================================
176      
177      /**
178       * Returns a copy of the signal produced by the most recent {@link #execute()
179       * execution} of this device.
180       * If this device has never been run, or if the input signal
181       * it processed most recently was <i>null</i>, the returned value will
182       * be <i>null</i>.
183       * <p>
184       * The returned signal may be safely cast to a {@code DigitalSignal}.</p>
185       */
186      public Signal getSignal()
187      {
188        return outputSignal == null ? null : outputSignal.clone();
189      }
190    
191      //============================================================================
192      // INTERFACE SignalProcessor AND SUPPORT THEREOF
193      //============================================================================
194      
195      /**
196       * Runs this device on the signal received from its input pipe and
197       * then executes the processor connected to its output pipe, if any.
198       * Both the input signal and the output produced by sampling it are
199       * remembered by this sampler and are available via the
200       * {@link #getMostRecentInput()} and {@link #getMostRecentOutput()} methods.
201       */
202      public void execute()
203      {
204        updateOutputSignal();
205        
206        outputPipe.execute();
207      }
208      
209      public void executeUpTo(SignalProcessor firstUnexecutedDevice)
210      {
211        //Quick exit if execution should not occur
212        if (firstUnexecutedDevice == this)
213          return;
214    
215        updateOutputSignal();
216        
217        //Tell text element of chain to do its work
218        outputPipe.executeUpTo(firstUnexecutedDevice);
219      }
220    
221      private void updateOutputSignal()
222      {
223        //Do the work and remember results
224        inputSignal = inputPipe.getSignal();
225        
226        if (inputSignal != null)
227        {
228          outputSignal = sample(inputSignal);
229          
230          if (!outputSignal.getDevicePath().endsWith(name))
231            outputSignal.appendToDevicePath(name);
232        }
233        else
234        {
235          outputSignal = null;
236        }
237      }
238      
239      public void executeFromStartOfChainUpTo(SignalProcessor firstUnexecutedDevice)
240      {
241        inputPipe.executeFromStartOfChainUpTo(firstUnexecutedDevice);
242      }
243    
244      /**
245       * Creates and returns a new signal that is the result of applying this
246       * sampler to the given input signal.
247       * <p>
248       * This sampler alters the analog signal in the same way that a mixer
249       * connected to a local oscillator that is tuned to a frequency equal
250       * to that of the sampling frequency of this mixer would.</p>
251       * <p>
252       * Note that the {@code input} signal and this sampler are
253       * not affected by calls to this method.  In other words this sampler
254       * will not remember the input signal sent in or the output
255       * signal produced.  This is in contrast to a call to the
256       * {@link #execute()} methods.</p>
257       * 
258       * @param input
259       *   the signal to be sampled.
260       *   
261       * @return a new digital signal that is the result of applying this sampler to
262       *         {@code input}.
263       */
264      public DigitalSignal sample(Signal input)
265      {
266        DigitalSignal output = null;
267        
268        if (input != null)
269        {
270          Signal temp = null;
271          
272          FrequencyRange inputRange = input.getCurrentRange();
273          if (inputRange.getLowFrequency().getValue().compareTo(BigDecimal.ZERO)==0 &&
274              inputRange.getHighFrequency().getValue().compareTo(BigDecimal.ZERO)==0)
275          {
276            temp = input.clone();  //A null-like frequency
277          }
278          else
279          {
280            temp = virtualMixer.mix(input);
281          }
282          output = new DigitalSignal(temp, samplingRate, bitsPerSample);
283        }
284        
285        return output;
286      }
287    }