//
// Copyright 2025 by Xavax, Inc. All Rights Reserved.
// Use of this software is allowed under the Xavax Open Software License.
// http://www.xavax.com/xosl.html
//

package com.xavax.dds;

/**
 * DDSTool is a program for evaluating the performance of different
 * combinations of clock frequency and phase accumulator width.
 */
public class DDSTool {
  private final static int noteCount = 88;
  private final static double step = 1.0594630943593;
  private final static double a0 = 27.5;
  private final static String format = "%d\t%f\t%f\t%f\t%d";
  private final static String usage =
    "usage: ddsTool accumulatorWidth clockFrequency";

  private int maxPosition = 0;
  private double maxVariance = 0.0;
  private double totalVariance = 0.0;
  private Note[] notes;

  public static void main(final String[] args) {
    DDSTool tool = new DDSTool();
    if ( args.length == 2 ) {
      final int width = Integer.parseInt(args[0]);
      final int clock = Integer.parseInt(args[1]);
      String message = String.format("width= %d, clock=%d", width, clock);
      System.out.println(message);
      tool.initialize();
      tool.evaluate(width, clock);
      tool.output();
    }
    else {
      System.out.println(usage);
    }
  }

  /**
   * For each note, compute the addend (divisor) and variance.
   *
   * @param width  the number of bits in the phase accumulator.
   * @param clock  the master clock frequency.
   */
  public void evaluate(final int width, final int clock) {
    long values = (long) Math.pow(2, width);
    for ( Note note : notes ) {
      int addend = computeAddend(values, clock, note.getIdeal());
      double actual = computeFrequency(values, clock, addend);
      note.setAddend(addend);
      note.setComputed(actual);
      double variance = note.getVariance();
      if ( variance > maxVariance ) {
	maxVariance = variance;
	maxPosition = note.getPosition();
      }
      totalVariance += variance;
    }
  }

  /**
   * Compute the amount that must be added to the phase accumulator
   * each cycle to synthesize the desired frequency.
   *
   * @param values  the number of possible values in the phase accumulator.
   * @param clock   the master clock frequency.
   * @param ideal   the ideal frequency.
   */
  public int computeAddend(final long values, final int clock, final double ideal ) {
    final double addend = ((double) values * ideal) / (double) clock;
    return (int) Math.round(addend);
  }

  /**
   * Compute the frequency that will be produced from the specified addend.
   *
   * @param values  the number of possible values in the phase accumulator.
   * @param clock   the master clock frequency.
   * @param addend  the amount to add to the phase accumulator.
   */
  public double computeFrequency(final long values, final int clock,
				 final int addend) {
    return ((double) addend * (double) clock) / (double) values;
  }

  /**
   * Initialize the array of Notes.
   */
  public void initialize() {
    double frequency = a0;
    notes = new Note[noteCount];
    for ( int position = 0; position < noteCount; ++position ) {
      notes[position] = new Note(position, frequency);
      frequency *= step;
    }
  }

  /**
   * Output the array of notes.
   */
  public void output() {
    for ( Note note : notes ) {
      final String message = String.format(format, note.getPosition(),
					   note.getIdeal(), note.getComputed(),
					   note.getVariance(), note.getAddend());
      System.out.println(message);
    }
    System.out.println("max variance:     " + maxVariance + " at position " + maxPosition);
    double average = totalVariance / noteCount;
    System.out.println("average variance: " + average);
  }
}
