javawaveform

How to get this pulse wave to have a finer transtion between duty cycle samples in Java


I am building a pulse wave generator that extends a square wave generator.

My problem is I want the pulse width modulation transition to be smoother I am trying to make the middle sample between the peak and trough of the wave to move smoothly up or down while the width is modulated. This is so higher sounds such as C7 at 2093 Hz don't sound clicky as the duty cycle is modulated.

What needs to be done?

Sample rate is 44100 Bit rate is 16 ampLimit is the highest possible value. duration is the length of waveform in seconds duty cycle is the starting cycle of the waveform

public class PulseGenerator extends SquareGenerator {

    // constants
    public static final double DEF_MIN_DUTY_CYCLE = 0.05;
    public static final double DEF_MAX_DUTY_CYCLE = 0.95;
    public static final double DEF_CYCLE_FREQ = 2;
    public static final double DEF_HOLD_CYCLE = 0;

    // instance variables
    double minDutyCycle; // minimum value of duty cycle
    double maxDutyCycle; // maximum value of duty cycle
    double cycleFreq;
    double holdCycle; // if more than zero, the wave will hold the modulation for that period
    double dutyCycleRange;  // maxDutyCycle - minDutyCycle
    boolean setDirection;

    // constructor
    public PulseGenerator(double amplitude, double frequency, int bitRate,
            double duration, double dutyCycle, double minDutyCycle,
            double maxDutyCycle, double cycleFreq, double holdCycle) {
        super(amplitude, frequency, bitRate, duration, dutyCycle);
        // sample data
        squareSample = new int[sampleLength];
        calculateAmpLimit();
        this.dutyCycle = dutyCycle;
        waveLength = SAMPLE_RATE / this.frequency;
        this.minDutyCycle = minDutyCycle;
        this.maxDutyCycle = maxDutyCycle;
        this.cycleFreq = cycleFreq * SAMPLE_RATE;
        this.holdCycle = holdCycle * SAMPLE_RATE;
        dutyCycleRange = this.maxDutyCycle - this.minDutyCycle;
        setDirection = false;
    }

    // one arg cunstructor
    public PulseGenerator(double frequency) {
        this(AMPLITUDE, frequency, BIT_RATE, DURATION, DEF_DUTY_CYCLE,
                DEF_MIN_DUTY_CYCLE, DEF_MAX_DUTY_CYCLE, DEF_CYCLE_FREQ,
                DEF_HOLD_CYCLE);
    }

    // no args constructor
    public PulseGenerator() {
        this(AMPLITUDE, FREQUENCY, BIT_RATE, DURATION, DEF_DUTY_CYCLE,
                DEF_MIN_DUTY_CYCLE, DEF_MAX_DUTY_CYCLE, DEF_CYCLE_FREQ,
                DEF_HOLD_CYCLE);
    }

    // generate waveform method
    @Override
    public int[] generateWaveForm() {

        // define cycle point
        int cyclePoint = (int)(cycleFreq / 2 * ((dutyCycle * dutyCycleRange) + minDutyCycle));

        // generate the actual waveform
        for (int i = 0, j = 0; i < sampleLength; i++, j++) {

            double waveCycleRatio = waveLength * dutyCycle;

            // same as square generate method
            if (j - waveCycleRatio < 0.0) {
                finePoint = 1.0;
            } else if (j - waveCycleRatio >= 0.0 
                    && j - waveCycleRatio < 1) {
                finePoint = 1 - (j - waveCycleRatio);
            } else if (j - waveLength < 0.0) {
                finePoint = -1.0;
            } else if (j - waveLength >= 0.0) {
                finePoint = -1 + (waveLength - j);
            }
            if (j >= waveLength) {
                j = 1;
            }
            point = (int)finePoint * ampLimit;
            squareSample[i] = point;

            if (holdCycle > 0) {
                holdCycle--;
            } else {
                dutyCycle = (cyclePoint / (cycleFreq / 2) * dutyCycleRange)
                        + minDutyCycle;
                if (cyclePoint < cycleFreq / 2 && !setDirection) {
                    cyclePoint++;
                } else if (cyclePoint >= cycleFreq / 2 && !setDirection) {
                    cyclePoint--;
                    setDirection = true;
                } else if (cyclePoint > 0 && setDirection) {
                    cyclePoint--;
                } else if (cyclePoint <= 0 && setDirection) {
                    cyclePoint++;
                    setDirection = false;
                }
            }
        }

        // return the sample data
        return squareSample;
    }

}

Solution

  • I actually found out what was wrong, first I was converting the fine point to an int before I multiplied it by the amp limit. second the code for the fine point needed to be repositioned and multiplied by two.

    // generate the actual waveform
            for (int i = 0, j = 0; i < sampleLength; i++, j++) {
    
                double waveCycleRatio = waveLength * dutyCycle;
    
                // same as square
                if (j - waveCycleRatio < 0.0) {
                    finePoint = 1.0;
                } else if (j - waveCycleRatio >= 0.0 
                        && j - waveCycleRatio < 1) {
                    finePoint = 0 - (j - waveCycleRatio - 0.5) * 2;
                } else if (j - waveLength < 0.0) {
                    finePoint = -1.0;
                } else if (j - waveLength >= 0.0) {
                    finePoint = (j - waveLength - 0.5) * 2;
                }
                if (j >= waveLength) {
                    j = 1;
                }
                point = (int)(finePoint * ampLimit);
                squareSample[i] = point;
    
                if (holdCycle > 0) {
                    holdCycle--;
                } else {
                    dutyCycle = (cyclePoint / (cycleFreq / 2) * dutyCycleRange)
                            + minDutyCycle;
                    if (cyclePoint < cycleFreq / 2 && !setDirection) {
                        cyclePoint++;
                    } else if (cyclePoint >= cycleFreq / 2 && !setDirection) {
                        cyclePoint--;
                        setDirection = true;
                    } else if (cyclePoint > 0 && setDirection) {
                        cyclePoint--;
                    } else if (cyclePoint <= 0 && setDirection) {
                        cyclePoint++;
                        setDirection = false;
                    }
                }
            }