/*
 * 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;

import java.util.Arrays;

/**
 * Differential BPSK Synthesizer.   In an analog PSK31 transmitter we might do this by 
 * creating square wave transitions between +1 and -1 and then using a 15Hz LPF 
 * to get rid of the harmonics (which results in a cosine-wave transition 
 * between high and low bits.  This class just uses a lookup table to generate 
 * the cosine-like high-to-low and low-to-high transitions.
 * 
 * The output of this class is meant to be multiplied in the time domain with
 * a local oscillator to generate a PSK31 signal at the desired frequency.
 * 
 * @author Andrew T. Flowers
 */
public class DbpskDDS {

    private final double sr;
    
    // lookup table for bit transitions
    double[] high;              //high-to-high, all +1.0
    double[] low;               // low-to-low all -1.0
    double[] transition_low;    //high-to-low, cosine wave 0-to-pi
    double[] transition_high;   //low-to-high, -cosine wave 0-to-pi
    
    // bit duration, based on baud rate.
    private final double bitDuration;
    
    // calculated from baud rate and sample rate
    private final int samplesPerBit;

    // since we actually need *differential* binary PSK when sending PSK31 we 
    // need to keep track of the differential.  In varicode, 0's actually mean
    // "reverse phase" and 1's mean "keep the same phase".  This flag is used
    // to keep track of "non-differential" high or low state that we actually
    // want to send to the downstream mixer. 
    boolean isHigh = false;
    
    /**
     *  Creates a new Differential BPSK Synthesizer.
     * 
     * @param sampleRate 
     * @param baudRate (e.g., 31.25 for PSK31)
     */
    public DbpskDDS(double sampleRate, double baudRate) {
        this.sr = sampleRate;
        bitDuration = 1.0 / baudRate;
        samplesPerBit = (int) Math.round(bitDuration * sr);
        makeDdsArrays();
    }

    /**
     * Gets the baseband samples for the supplied character.
     * @param c
     * @return 
     */
    public double[] getSamples(char c) {
        return getSamples("" + c);
    }
    
    /**
     * Gets the baseband samples for the supplied character.  The string
     * should be one that is present in the varicode table.  Usually this
     * is a string with a single character in it.
     * 
     * @param s
     * @return 
     */
    public double[] getSamples(String s) {
        String pattern = Varicode.getPattern(s);
        double[] rVal = new double[pattern.length() * samplesPerBit];

        int pos = 0;
        double[] bitSamples;
        for (int i = 0; i < pattern.length(); i++) {
            char c = pattern.charAt(i);
            if (c == '1') {
                bitSamples = keepPhase();
            } else {
                bitSamples = reversePhase();
            }
            System.arraycopy(bitSamples, 0, rVal, pos, bitSamples.length);
            pos += bitSamples.length;
        }
        return rVal;
    }

    private double[] keepPhase() {
        if (isHigh) {
            return high;
        } else {
            return low;
        }
    }

    private double[] reversePhase() {
        double[] rVal;
        if (isHigh) {
            rVal = transition_low;
        } else {
            rVal = transition_high;
        }
        isHigh = !isHigh;
        return rVal;
    }

    /**
     * Generates the four waveforms in the lookup table.
     */
    private void makeDdsArrays() {

        high = new double[samplesPerBit];
        low = new double[samplesPerBit];
        transition_low = new double[samplesPerBit];
        transition_high = new double[samplesPerBit];

        Arrays.fill(high, 1.0);
        Arrays.fill(low, -1.0);

        for (int i = 0; i < transition_low.length; i++) {
            transition_low[i] = Math.cos(Math.PI * (i / ((double) samplesPerBit)));
        }
        for (int i = 0; i < transition_high.length; i++) {
            transition_high[i] = -Math.cos(Math.PI * (i / ((double) samplesPerBit)));
        }

    }
}
