/*
 * Copyright 2015 Andrew T. Flowers, K0SM
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dopplerpsk.dsp;

/**
 * Local Oscillator for the PSK transmitter.  This class can be updated to
 * provide either a conventional steady carrier or updated to provide
 * Doppler correction using a linear chirp to correct for Doppler shift
 * or other system component that requires a variable frequency oscillator. The
 * chirp algorithm is designed to be continuous so that there are no first-
 * order discontinuities in the frequency domain.
 * 
 * @author Andrew T. Flowers
 */
public class LocalOscillator  {

    // Linear chirp:
    //
    // (see https://en.wikipedia.org/wiki/Chirp)
    //
    // Instantaneous frequency f(t):
    //
    // f(t) = f0 + kt
    // where k = (f1 - f0) / T
    // we will get k in Hz/s from the orbital propagator
    // and update this value in real time.
    //
    // To generate a local osciallator that sweeps we calculate
    // the phase of the signal over time--this allows us to
    // maintain phase continuity when the sweep rate changes:
    //
    // phase(t) = phase(t0) + 2pi(f0*t + ((k/2)*t^2)
    //
    // time domain signal is first derivative of phase,
    // i.e., just take the sine of the phase eq:
    //
    // x[t] = sin(phase(t))
    private double dopplerCenter = 2000.0;  //initial frequency (Hz)
    
    private double f0 = 2000.0;             //initial frequency (Hz)
    private volatile double k = 0;          //frequency change rate in (Hz/s)
    private double ph0 = 0;                 //initial phase (radians)
    private double ph = 0;                  //current phase (radians)
    private double t = 0;                   // time (seconds)

    private final double sr;                // sample rate
    private int sampleSinceUpdate = 0;      // counts samples


    public LocalOscillator(double sr, double dopplerCenter) {
        this.sr = sr;
        this.dopplerCenter = dopplerCenter;
    }

    public double getInstantaneousFrequency() {
        return f0;
    }
    
    public void setInstantaneousFrequency(double f) {
        f0 = f;
    }
    
    public double getDopplerRate() {
        return k;
    }
    
   /**
    * Used to change the carrier frequency of the LO
    * @param absDopplerShift current Doppler shift in Hz
    * @param chirpRate current Doppler rate in Hz/s (set to zero for constant frequency)
    */
    public synchronized void adjustCarrierFrequency(double absDopplerShift, double chirpRate) {

        double elapsedTime = ((double) sampleSinceUpdate) / sr;

        if (sampleSinceUpdate > 0) {  // just in case we get two simultaneous events
            
            //create a new starting place using the last phase and frequency
            // as the starting place.
            f0 = f0 + (elapsedTime * k);
            ph0 = ph;
            t = 1.0 / sr;  // advance t one sample so we don't repeat the last phase value
        }
        //set new chirp rate
        k = chirpRate;

        sampleSinceUpdate = 0;

    }

    public synchronized double getNextSample() {

        ph = ph0 + 2 * Math.PI * (f0 * t + ((k / 2) * Math.pow(t, 2)));
        t += 1.0 / sr;

        // return x(t)
        sampleSinceUpdate++;
        //System.out.println(Math.sin(ph));
        return Math.sin(ph);
    }

    public double getCenterFrequency() {
        return dopplerCenter;
    }

}
