// StringWave.java (C) 2001 by Paul Falstad, www.falstad.com

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
import java.applet.Applet;
import java.applet.AudioClip;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.util.Random;
import java.awt.image.MemoryImageSource;
import java.lang.Math;
import java.awt.event.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

class StringWaveCanvas extends Canvas {
    StringWaveFrame pg;
    StringWaveCanvas(StringWaveFrame p) {
        pg = p;
    }
    public Dimension getPreferredSize() {
        return new Dimension(300,400);
    }
    public void update(Graphics g) {
        pg.updateStringWave(g);
    }
    public void paint(Graphics g) {
        pg.updateStringWave(g);
    }
};

class StringWaveLayout implements LayoutManager {
    public StringWaveLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
        return new Dimension(500, 500);
    }
    public Dimension minimumLayoutSize(Container target) {
        return new Dimension(100,100);
    }
    public void layoutContainer(Container target) {
        int barwidth = 0;
        int i;
        for (i = 1; i < target.getComponentCount(); i++) {
            Component m = target.getComponent(i);
            if (m.isVisible()) {
                Dimension d = m.getPreferredSize();
                if (d.width > barwidth)
                    barwidth = d.width;
            }
        }
        Insets insets = target.insets();
        int targetw = target.size().width - insets.left - insets.right;
        int cw = targetw-barwidth;
        int targeth = target.size().height - (insets.top+insets.bottom);
        target.getComponent(0).move(insets.left, insets.top);
        target.getComponent(0).resize(cw, targeth);
        cw += insets.left;
        int h = insets.top;
        for (i = 1; i < target.getComponentCount(); i++) {
            Component m = target.getComponent(i);
            if (m.isVisible()) {
                Dimension d = m.getPreferredSize();
                if (m instanceof Scrollbar)
                    d.width = barwidth;
                if (m instanceof Label) {
                    h += d.height/5;
                    d.width = barwidth;
                }
                m.move(cw, h);
                m.resize(d.width, d.height);
                h += d.height;
            }
        }
    }
};

public class StringWave extends Applet implements ComponentListener {
    static StringWaveFrame ogf;
    void destroyFrame() {
        if (ogf != null)
            ogf.dispose();
        ogf = null;
        repaint();
    }
    boolean started = false;
    public void init() {
        addComponentListener(this);
    }

    public static void main(String args[]) {
        ogf = new StringWaveFrame(null);
        ogf.init();
    }

    void showFrame() {
        if (ogf == null) {
            started = true;
            ogf = new StringWaveFrame(this);
            ogf.init();
            repaint();
        }
    }

    public void paint(Graphics g) {
        String s = "Applet is open in a separate window.";
        if (!started)
            s = "Applet is starting.";
        else if (ogf == null)
            s = "Applet is finished.";
        else
            ogf.show();
        g.drawString(s, 10, 30);
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) { showFrame(); }
    public void componentResized(ComponentEvent e) {}

    public void destroy() {
        if (ogf != null)
            ogf.dispose();
        ogf = null;
        repaint();
    }
};

class StringWaveFrame extends Frame
  implements ComponentListener, ActionListener, AdjustmentListener,
             MouseMotionListener, MouseListener, ItemListener {

    PlayThread playThread;

    Dimension winSize;
    Image dbimage;

    Random random;
    int maxTerms = 60;
    int maxMaxTerms = 160;
    int sampleCount;
    double sinTable[][];
    public static final double epsilon = .00001;
    public static final double epsilon2 = .003;

    public String getAppletInfo() {
        return "StringWave Series by Paul Falstad";
    }

    Button sineButton;
    Button triangleButton;
    Button blankButton;
    Button resonanceButton;
    Checkbox stoppedCheck;
    Checkbox forceCheck;
    Checkbox soundCheck;
    Checkbox touchCheck;
    Checkbox backwardsCheck;
    Checkbox logCheck;
    Choice modeChooser;
    Choice displayChooser;
    Scrollbar dampingBar;
    Scrollbar speedBar;
    Scrollbar forceBar;
    Scrollbar loadBar;
    Scrollbar tensionBar;
    double magcoef[];
    double dampcoef;
    double phasecoef[];
    double phasecoefcos[];
    double phasecoefadj[];
    //double magcoefdisp[];
    //double phasecoefdisp[];
    //double phasecoefcosdisp[];
    double forcebasiscoef[];
    double omega[];
    static final double pi = 3.14159265358979323846;
    double step;
    double func[];
    double funci[];
    int selectedCoef;
    int magnitudesY;
    static final int SEL_NONE = 0;
    static final int SEL_FUNC = 1;
    static final int SEL_MAG = 2;
    static final int MODE_PLUCK = 0;
    static final int MODE_SHAPE = 1;
    static final int MODE_TOUCH = 997;
    static final int MODE_FORCE = 998;
    static final int MODE_BOW = 999;
    static final int DISP_PHASE = 0;
    static final int DISP_LEFTRIGHT = 1;
    static final int DISP_PHASECOS = 2;
    static final int DISP_PHASORS = 3;
    static final int DISP_MODES = 4;
    int selection;
    int dragX, dragY;
    boolean dragging;
    boolean bowing;
    boolean bowCaught;
    boolean forceApplied;
    double t;
    double forceMag;
    int pause;
    int forceBarValue;
    double forceTimeZero;
    int tensionBarValue;
    Color gray1 = new Color(76,  76,  76);
    Color gray2 = new Color(127, 127, 127);


    int getrand(int x) {
        int q = random.nextInt();
        if (q < 0) q = -q;
        return q % x;
    }
    StringWaveCanvas cv;
    StringWave applet;
    boolean java2;

    StringWaveFrame(StringWave a) {
        super("Loaded String Applet v1.5a");
        applet = a;
    }

    public void init() {
        String jv = System.getProperty("java.class.version");
        double jvf = new Double(jv).doubleValue();
        if (jvf >= 48)
            java2 = true;

        selectedCoef = -1;
        setLayout(new StringWaveLayout());
        cv = new StringWaveCanvas(this);
        cv.addComponentListener(this);
        cv.addMouseMotionListener(this);
        cv.addMouseListener(this);
        add(cv);
        add(sineButton = new Button("Fundamental"));
        sineButton.addActionListener(this);
        add(triangleButton = new Button("Center Pluck"));
        triangleButton.addActionListener(this);
        add(blankButton = new Button("Clear"));
        blankButton.addActionListener(this);
        stoppedCheck = new Checkbox("Stopped");
        stoppedCheck.addItemListener(this);
        add(stoppedCheck);
        forceCheck = new Checkbox("Driving Force", false);
        forceCheck.addItemListener(this);
        add(forceCheck);
        soundCheck = new Checkbox("Sound", false);
        soundCheck.addItemListener(this);
        if (java2)
            add(soundCheck);
        touchCheck = new Checkbox("Touched in Center", false);
        touchCheck.addItemListener(this);
        /*add(touchCheck);*/
        backwardsCheck = new Checkbox("Run Backwards", false);
        backwardsCheck.addItemListener(this);
        // add(backwardsCheck);
        logCheck = new Checkbox("Log View", false);
        logCheck.addItemListener(this);
        add(logCheck);

        modeChooser = new Choice();
        modeChooser.add("Mouse = Pluck string");
        modeChooser.add("Mouse = Shape string");
        /*modeChooser.add("Mouse = Touch string");
          modeChooser.add("Mouse = Apply force");*/
        modeChooser.addItemListener(this);
        add(modeChooser);

        displayChooser = new Choice();
        displayChooser.add("Display Phases");
        displayChooser.add("Display Left+Right");
        displayChooser.add("Display Phase Cosines");
        displayChooser.add("Display Phasors");
        displayChooser.add("Display Modes");
        displayChooser.addItemListener(this);
        add(displayChooser);

        add(new Label("Simulation Speed", Label.CENTER));
        add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 85, 1, 1, 200));
        speedBar.addAdjustmentListener(this);

        add(new Label("Damping", Label.CENTER));
        add(dampingBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 400));
        dampingBar.addAdjustmentListener(this);

        add(new Label("Force Frequency", Label.CENTER));
        forceBarValue = 5;
        add(forceBar = new Scrollbar(Scrollbar.HORIZONTAL,
                                     forceBarValue, 1, 1, 30));
        forceBar.addAdjustmentListener(this);

        add(resonanceButton = new Button("Resonance Freq"));
        resonanceButton.addActionListener(this);

        add(new Label("Number of Loads", Label.CENTER));
        add(loadBar = new Scrollbar(Scrollbar.HORIZONTAL,
                                    maxTerms, 1, 2, maxMaxTerms));
        loadBar.addAdjustmentListener(this);
        setLoadCount();

        tensionBarValue = 16;
        add(new Label("Tension", Label.CENTER));
        add(tensionBar = new Scrollbar(Scrollbar.HORIZONTAL,
                                       tensionBarValue, 1, 1, 100));
        tensionBar.addAdjustmentListener(this);

        try {
            String param = applet.getParameter("PAUSE");
            if (param != null)
                pause = Integer.parseInt(param);
        } catch (Exception e) { }

        magcoef = new double[maxMaxTerms];
        phasecoef = new double[maxMaxTerms];
        phasecoefcos = new double[maxMaxTerms];
        phasecoefadj = new double[maxMaxTerms];
        //magcoefdisp = new double[maxMaxTerms];
        //phasecoefdisp = new double[maxMaxTerms];
        //phasecoefcosdisp = new double[maxMaxTerms];
        forcebasiscoef = new double[maxMaxTerms];
        func = new double[maxMaxTerms+1];
        funci = new double[maxMaxTerms+1];

        random = new Random();
        setDamping();
        reinit();
        cv.setBackground(Color.black);
        cv.setForeground(Color.lightGray);
        resize(800, 600);
        handleResize();
        Dimension x = getSize();
        Dimension screen = getToolkit().getScreenSize();
        setLocation((screen.width  - x.width)/2,
                    (screen.height - x.height)/2);
        show();
    }

    void reinit() {
        doSine();
    }

    void handleResize() {
        Dimension d = winSize = cv.getSize();
        if (winSize.width == 0)
            return;
        dbimage = createImage(d.width, d.height);
    }

    void doSine() {
        int x;
        for (x = 0; x != sampleCount; x++) {
            func[x] = java.lang.Math.sin(x*step);
        }
        func[sampleCount] = func[0];
        transform(true);
    }

    void doTriangle() {
        int x;
        for (x = 0; x <= sampleCount/2; x++)
            func[sampleCount-x] = func[x] = 2.0*x/sampleCount;
        func[sampleCount] = func[0];
        transform(true);
    }

    void doBlank() {
        int x;
        for (x = 0; x <= sampleCount; x++)
            func[x] = 0;
        transform(true);
    }

    void transform(boolean novel) {
        int x, y;
        t = 0;
        for (y = 1; y != maxTerms; y++) {
            double a = 0;
            double b = 0;
            for (x = 1; x != sampleCount; x++) {
                a += sinTable[x][y]*func[x];
                b -= sinTable[x][y]*funci[x];
            }
            a *= 2.0/sampleCount;
            b *= 2.0/(sampleCount*omega[y]);
            if (a < epsilon && a > -epsilon) a = 0;
            if (b < epsilon && b > -epsilon) b = 0;
            if (novel)
                b = 0;
            double r = java.lang.Math.sqrt(a*a+b*b);
            magcoef[y] = r;
            double ph2 = java.lang.Math.atan2(b, a);
            phasecoefadj[y] = ph2;
            phasecoef[y] = ph2;
        }
        updateSound();
    }

    void updateSound() {
        if (playThread != null)
            playThread.soundChanged();
    }

    int getPanelHeight() { return winSize.height / 3; }

    void centerString(Graphics g, String s, int y) {
        FontMetrics fm = g.getFontMetrics();
        g.drawString(s, (winSize.width-fm.stringWidth(s))/2, y);
    }

    public void paint(Graphics g) {
        cv.repaint();
    }

    long lastTime;

    public void updateStringWave(Graphics realg) {
        if (winSize == null || winSize.width == 0)
            return;
        Graphics g = dbimage.getGraphics();
        boolean allQuiet = true;
        double dampmult = 1;
        if (!stoppedCheck.getState()) {
            if (bowing) {
                doBow();
                allQuiet = false;
            }
            int val = speedBar.getValue();
            //if (val > 40)
            //val += getrand(10);
            if (forceCheck.getState()) {
                doForce();
                allQuiet = false;
            } else
                forceMag = 0;
            long sysTime = System.currentTimeMillis();
            double tadd = 0;
            if (lastTime != 0)
                tadd = java.lang.Math.exp(val/20.)*(.1/1500)*(sysTime-lastTime);
            if (backwardsCheck.getState())
                t -= tadd;
            else
                t += tadd;
            lastTime = sysTime;
            dampmult = Math.exp(dampcoef*tadd);
        } else
            lastTime = 0;
        g.setColor(cv.getBackground());
        g.fillRect(0, 0, winSize.width, winSize.height);
        g.setColor(cv.getForeground());
        int i;
        int ox = -1, oy = -1;
        int panelHeight = getPanelHeight();
        int midy = panelHeight / 2;
        int halfPanel = panelHeight / 2;
        double ymult = .75 * halfPanel;
        for (i = -1; i <= 1; i++) {
            g.setColor((i == 0) ? gray2 : gray1);
            g.drawLine(0,             midy+(i*(int) ymult),
                       winSize.width, midy+(i*(int) ymult));
        }
        g.setColor(gray2);
        g.drawLine(winSize.width/2, midy-(int) ymult,
                   winSize.width/2, midy+(int) ymult);
        if (dragging && selection == SEL_FUNC) {
            g.setColor(Color.cyan);
            allQuiet = true;
            for (i = 0; i != sampleCount+1; i++) {
                int x = winSize.width * i / sampleCount;
                int y = midy - (int) (ymult * func[i]);
                if (ox != -1)
                    g.drawLine(ox, oy, x, y);
                ox = x;
                oy = y;
            }
        }
        if (!stoppedCheck.getState()) {
            if (touchCheck.getState())
                doTouch();
            for (i = 1; i != maxTerms; i++)
                magcoef[i] *= dampmult;
        }

        double magcoefdisp[] = magcoef;
        double phasecoefdisp[] = phasecoef;
        double phasecoefcosdisp[] = phasecoefcos;

        if (dragging && selection == SEL_FUNC) {
            lastTime = 0;
        } else {
            g.setColor(Color.white);
            ox = -1;
            int j;
            for (j = 1; j != maxTerms; j++) {
                if (magcoef[j] < epsilon && magcoef[j] > -epsilon) {
                    magcoef[j] = phasecoef[j] = phasecoefadj[j] = 0;
                    continue;
                }
                allQuiet = false;
                phasecoef[j] = (omega[j]*t+phasecoefadj[j]) % (2*pi);
                if (phasecoef[j] > pi)
                    phasecoef[j] -= 2*pi;
                else if (phasecoef[j] < -pi)
                    phasecoef[j] += 2*pi;
                phasecoefcos[j] = java.lang.Math.cos(phasecoef[j]);
            }
            if (forceApplied) {
                allQuiet = false;
                magcoefdisp = new double[maxTerms];
                phasecoefdisp = new double[maxTerms];
                phasecoefcosdisp = new double[maxTerms];
                for (i = 1; i < maxTerms; i++) {
                    double ph = phasecoef[i];
                    double a = magcoef[i] * phasecoefcos[i];
                    double b = magcoef[i] * java.lang.Math.sin(ph);
                    a += forcebasiscoef[i];
                    double r = java.lang.Math.sqrt(a*a+b*b);
                    magcoefdisp[i] = r;
                    double ph2 = java.lang.Math.atan2(b, a);
                    phasecoefdisp[i] += ph2;
                    phasecoefcosdisp[i] = (r > 0) ? a/r : 0;
                }
            }

            int dotSize = (sampleCount < 40) ? 5 : 0;
            int funcDotSize = dotSize;
            int forcePos = (forceMag == 0) ? -1 : sampleCount/2;
            for (i = 0; i != sampleCount+1; i++) {
                int x = winSize.width * i / sampleCount;
                double dy = 0;
                for (j = 1; j != maxTerms; j++)
                    dy += magcoefdisp[j] *
                        sinTable[i][j] * phasecoefcosdisp[j];
                func[i] = dy;
                int y = midy - (int) (ymult * dy);
                if (ox != -1)
                    g.drawLine(ox, oy, x, y);
                if (dotSize > 0 && i != 0 && i != sampleCount)
                    g.fillOval(x-dotSize/2, y-dotSize/2, dotSize, dotSize);
                if (i == forcePos) {
                    int yl = (int) (ymult*forceMag*8);
                    if (yl > 7 || yl < -7) {
                        int y2 = y - yl;
                        int forcedir = (forceMag < 0) ? -1 : 1;
                        g.drawLine(x, y, x, y2);
                        g.drawLine(x, y2, x+5, y2+5*forcedir);
                        g.drawLine(x, y2, x-5, y2+5*forcedir);
                    }
                }
                ox = x;
                oy = y;
            }
        }
        if (selectedCoef != -1 && !dragging &&
            (magcoefdisp[selectedCoef] > .04 ||
             magcoefdisp[selectedCoef] < -.04)) {
            g.setColor(Color.yellow);
            ox = -1;
            ymult *= magcoefdisp[selectedCoef];
            for (i = 0; i != sampleCount+1; i++) {
                int x = winSize.width * i / sampleCount;
                double dy = sinTable[i][selectedCoef] *
                    phasecoefcosdisp[selectedCoef];
                int y = midy - (int) (ymult * dy);
                if (ox != -1)
                    g.drawLine(ox, oy, x, y);
                ox = x;
                oy = y;
            }
        }
        int termWidth = getTermWidth();
        ymult = .6 * halfPanel;
        g.setColor(Color.white);
        if (displayChooser.getSelectedIndex() == DISP_PHASE ||
            displayChooser.getSelectedIndex() == DISP_PHASECOS)
            magnitudesY = panelHeight;
        else
            magnitudesY = panelHeight*2;
        midy = magnitudesY + (panelHeight / 2) + (int) ymult/2;
        centerString(g, "Harmonics: Magnitudes", magnitudesY+(int) (panelHeight*.16));
        g.setColor(gray2);
        g.drawLine(0, midy, winSize.width, midy);
        g.setColor(gray1);
        g.drawLine(0, midy-(int)ymult, winSize.width, midy-(int) ymult);
        g.drawLine(0, midy+(int)ymult, winSize.width, midy+(int) ymult);
        int dotSize = termWidth-3;
        if (dotSize < 3)
            dotSize = 3;
        if (dotSize > 9)
            dotSize = 9;
        for (i = 1; i != maxTerms; i++) {
            int t = termWidth * (i-1) + termWidth/2;
            int y = midy - (int) (logcoef(magcoefdisp[i])*ymult);
            g.setColor(i == selectedCoef ? Color.yellow : Color.white);
            g.drawLine(t, midy, t, y);
            g.fillOval(t-dotSize/2, y-dotSize/2, dotSize, dotSize);
        }

        if (displayChooser.getSelectedIndex() == DISP_PHASE ||
            displayChooser.getSelectedIndex() == DISP_PHASECOS) {
            g.setColor(Color.white);
            boolean cosines =
                displayChooser.getSelectedIndex() == DISP_PHASECOS;
            centerString(g, cosines ? "Harmonics: Phase Cosines" : "Harmonics: Phases",
                         (int) (panelHeight*2.10));
            ymult = .75 * halfPanel;
            midy = ((panelHeight * 5) / 2);
            for (i = -2; i <= 2; i++) {
                if (cosines && (i == 1 || i == -1))
                    continue;
                g.setColor((i == 0) ? gray2 : gray1);
                g.drawLine(0,             midy+(i*(int) ymult)/2,
                           winSize.width, midy+(i*(int) ymult)/2);
            }
            if (!cosines)
                ymult /= pi;
            for (i = 1; i != maxTerms; i++) {
                int t = termWidth * (i-1) + termWidth/2;
                double ph = (cosines) ? phasecoefcosdisp[i] : phasecoefdisp[i];
                if (magcoef[i] > -epsilon2/4 && magcoefdisp[i] < epsilon2/4)
                    ph = 0;
                int y = midy - (int) (ph*ymult);
                g.setColor(i == selectedCoef ? Color.yellow : Color.white);
                g.drawLine(t, midy, t, y);
                g.fillOval(t-dotSize/2, y-dotSize/2, dotSize, dotSize);
            }
        } else if (displayChooser.getSelectedIndex() == DISP_LEFTRIGHT) {
            midy = panelHeight + panelHeight/2;
            halfPanel = panelHeight/2;
            ymult = .75 * halfPanel;
            for (i = -1; i <= 1; i++) {
                g.setColor((i == 0) ? gray2 : gray1);
                g.drawLine(0,             midy+(i*(int) ymult),
                           winSize.width, midy+(i*(int) ymult));
            }
            g.setColor(gray2);
            g.drawLine(winSize.width/2, midy-(int) ymult,
                       winSize.width/2, midy+(int) ymult);
            ox = -1;
            int oy2 = -1;
            int subsamples = 4;
            for (i = 0; i != sampleCount*subsamples+1; i++) {
                int x = winSize.width * i / (subsamples*sampleCount);
                double dy1 = 0;
                double dy2 = 0;
                int j;
                double stepi = step*i/subsamples;
                for (j = 1; j != maxTerms; j++) {
                    if (magcoefdisp[j] == 0)
                        continue;
                    double stepij = stepi*j;
                    double dp = magcoefdisp[j]*.5;
                    double phase = phasecoefdisp[j];
                    dy1 += dp*java.lang.Math.sin(stepij+phase);
                    dy2 += dp*java.lang.Math.sin(stepij-phase);
                }
                int y1 = midy - (int) (ymult * dy1);
                int y2 = midy - (int) (ymult * dy2);
                if (ox != -1) {
                    g.setColor(Color.cyan);
                    g.drawLine(ox, oy,  x, y1);
                    g.setColor(Color.green);
                    g.drawLine(ox, oy2, x, y2);
                }
                ox = x;
                oy  = y1;
                oy2 = y2;
            }
        } else if (displayChooser.getSelectedIndex() == DISP_PHASORS) {
            int sqw = (winSize.width-25)/3;
            int sqh = sqw;
            int y = panelHeight+(panelHeight-sqh)/2;
            dotSize = 5;
            for (i = 1; i <= 3; i++) {
                g.setColor(gray2);
                int leftX = (sqw+10)*(i-1);
                int centerX = leftX+sqw/2;
                int centerY = y+sqh/2;
                g.drawLine(leftX, centerY, leftX+sqw, centerY);
                g.drawLine(centerX, y, centerX, y+sqh);
                g.setColor(gray1);
                g.drawOval(centerX-sqw/2, centerY-sqh/2, sqw, sqh);
                g.setColor(i == selectedCoef ? Color.yellow : Color.white);
                g.drawRect(leftX, y, sqw, sqh);
                boolean getFx = (forceApplied || forceCheck.getState());
                int ax = (int) (phasecoefcosdisp[i]*magcoefdisp[i]*sqw*.5);
                int ay = (int) (java.lang.Math.sin(phasecoefdisp[i])*
                                magcoefdisp[i]*sqh*.5);
                int fx = (getFx) ? ((int) (forcebasiscoef[i]*sqw*.5)) : 0;
                g.drawLine(centerX+fx, centerY, centerX+ax, centerY-ay);
                g.fillOval(centerX+ax-dotSize/2, centerY-ay-dotSize/2,
                           dotSize, dotSize);
            }
        } else if (displayChooser.getSelectedIndex() == DISP_MODES) {
            int sqw = (winSize.width-25)/3;
            int sqh = (int) (sqw/pi);
            int topY = panelHeight;
            int leftX = 0;
            for (i = 1; i < sampleCount; i++) {
                if (!(magcoefdisp[i] > .06 ||
                      magcoefdisp[i] < -.06))
                    continue;
                g.setColor(gray2);
                int centerX = leftX+sqw/2;
                int centerY = topY+sqh/2;
                g.drawLine(leftX, centerY, leftX+sqw, centerY);
                g.drawLine(centerX, topY, centerX, topY+sqh);
                g.setColor(i == selectedCoef ? Color.yellow : Color.white);
                g.drawRect(leftX, topY, sqw, sqh);
                ox = -1;
                ymult = sqh*.5*magcoefdisp[i];
                int j;
                for (j = 0; j != sampleCount+1; j++) {
                    int x = leftX + sqw * j / sampleCount;
                    double dy = sinTable[j][i] * phasecoefcosdisp[i];
                    int y = centerY - (int) (ymult * dy);
                    if (ox != -1)
                        g.drawLine(ox, oy, x, y);
                    ox = x;
                    oy = y;
                }
                leftX += sqw+10;
                if (leftX + sqw > winSize.width) {
                    leftX = 0;
                    topY += sqh + 10;
                    if (topY+sqh > panelHeight*2)
                        break;
                }
            }
        }
        realg.drawImage(dbimage, 0, 0, this);
        if (!stoppedCheck.getState() && !allQuiet)
            cv.repaint(pause);
    }

    int getTermWidth() {
        int termWidth = winSize.width / maxTerms;
        if (termWidth < 2)
            termWidth = 2;
        int maxTermWidth = winSize.width/30;
        if (termWidth > maxTermWidth)
            termWidth = maxTermWidth;
        termWidth &= ~1;
        return termWidth;
    }

    void getVelocities() {
        int k, j;
        for (j = 0; j != sampleCount; j++) {
            double dy = 0;
            for (k = 0; k != sampleCount; k++)
                dy += magcoef[k] * sinTable[j][k] *
                    java.lang.Math.sin(phasecoef[k]) * omega[k];
            funci[j] = -dy;
        }
    }

    void setForce() {
        // adjust time zero to maintain continuity in the force func
        // even though the frequency has changed.
        double oldfreq = forceBarValue * omega[1] / 20.0;
        forceBarValue = forceBar.getValue();
        double newfreq = forceBarValue * omega[1] / 20.0;
        double adj = newfreq-oldfreq;
        forceTimeZero = t-oldfreq*(t-forceTimeZero)/newfreq;
    }

    void doForce() {
        double freq = forceBar.getValue() * omega[1] / 20.0;
        // was .01
        forceMag = java.lang.Math.cos((t-forceTimeZero)*freq)*.06;
        if (forceBar.getValue() == 1)
            forceMag *= 2;
        applyForce(maxTerms/2, forceMag);
    }

    void doTouch() {
        int x = sampleCount/2;
        double lim = .1;
        double val = func[x];
        double force = 0;
        if (val > lim)
            force = -(val-lim);
        else if (val < -lim)
            force = -(val+lim);
        else
            return;
        int y;
        for (y = 1; y != maxTerms; y++) {
            double coef = 0;
            for (int j = 1; j != sampleCount; j++) {
                double f = (j <= x) ? force*j/x :
                    force*(sampleCount-j)/(sampleCount-x);
                coef += sinTable[j][y]*f;
            }
            coef *= 2.0/sampleCount;

            double ph = phasecoefadj[y]+omega[y]*t; // XXX change elsewhere
            /// XXX outside loop, elsewhere too
            double a = magcoef[y] * java.lang.Math.cos(ph);
            double b = magcoef[y] * java.lang.Math.sin(ph);
            a += coef;
            double r = java.lang.Math.sqrt(a*a+b*b);
            magcoef[y] = r;
            double ph2 = java.lang.Math.atan2(b, a);
            phasecoefadj[y] += ph2-ph;
        }
    }

    void edit(MouseEvent e) {
        if (selection == SEL_NONE)
            return;
        int x = e.getX();
        int y = e.getY();
        switch (selection) {
        case SEL_MAG:   editMag(x, y);   break;
        case SEL_FUNC:  editFunc(x, y);  break;
        }
    }

    void editMag(int x, int y) {
        if (selectedCoef == -1)
            return;
        int panelHeight = getPanelHeight();
        double ymult = .6 * panelHeight/2;
        double midy = magnitudesY + (panelHeight / 2) + (int) ymult/2;
        double coef = -(y-midy) / ymult;
        coef = unlogcoef(coef);
        if (coef < -1)
            coef = -1;
        if (coef > 1)
            coef = 1;
        if (magcoef[selectedCoef] == coef)
            return;
        magcoef[selectedCoef] = coef;
        updateSound();
        cv.repaint(pause);
    }

    void editFunc(int x, int y) {
        if (modeChooser.getSelectedIndex() == MODE_PLUCK) {
            editFuncPluck(x, y);
            return;
        }
        if (modeChooser.getSelectedIndex() == MODE_TOUCH) {
            editFuncTouch(x, y);
            return;
        }
        if (modeChooser.getSelectedIndex() == MODE_BOW) {
            editFuncBow(x, y);
            return;
        }
        if (modeChooser.getSelectedIndex() == MODE_FORCE) {
            forceCheck.setState(false);
            editFuncForce(x, y);
            return;
        }
        if (dragX == x) {
            editFuncPoint(x, y);
            dragY = y;
        } else {
            // need to draw a line from old x,y to new x,y and
            // call editFuncPoint for each point on that line.  yuck.
            int x1 = (x < dragX) ? x : dragX;
            int y1 = (x < dragX) ? y : dragY;
            int x2 = (x > dragX) ? x : dragX;
            int y2 = (x > dragX) ? y : dragY;
            dragX = x;
            dragY = y;
            for (x = x1; x <= x2; x++) {
                y = y1+(y2-y1)*(x-x1)/(x2-x1);
                editFuncPoint(x, y);
            }
        }
    }

    void editFuncTouch(int xx, int yy) {
        dragging = false;
        int panelHeight = getPanelHeight();
        int midy = panelHeight / 2;
        int halfPanel = panelHeight / 2;
        int periodWidth = winSize.width;
        double ymult = .75 * halfPanel;
        int x = xx * sampleCount / periodWidth;
        double val = (midy - yy) / ymult;
        if (val > 1)
            val = 1;
        if (val < -1)
            val = -1;
        if (x < 1 || x >= sampleCount)
            return;
        //if (val > 0 && func[x] < val)
        //  return;
        //if (val < 0 && func[x] > val)
        //  return;
        int y;
        for (y = 1; y != maxTerms; y++) {
            double coef1 = sinTable[x][y];
            if (coef1 < 0)
                coef1 = -coef1;
            double coef = magcoef[y]*coef1;
            if (coef < 0)
                coef = -coef;
            double f = .02;
            if (coef < f)
                continue;
            int sign = (magcoef[y] < 0) ? -1 : 1;
            magcoef[y] = sign*f/coef1;
        }
    }

    void editFuncForce(int xx, int yy) {
        dragging = false;
        int panelHeight = getPanelHeight();
        int midy = panelHeight / 2;
        int halfPanel = panelHeight / 2;
        int periodWidth = winSize.width;
        double ymult = .75 * halfPanel;
        int x = xx * sampleCount / periodWidth;
        if (x < 1 || x >= sampleCount)
            return;
        double val = (midy - yy) / ymult;
        if (val > 1)
            val = 1;
        if (val < -1)
            val = -1;
        soundCheck.setState(false);
        applyForce(x, val);
        cv.repaint(pause);
    }

    void applyForce(int x, double val) {
        int y;
        for (y = 1; y != maxTerms; y++) {
            double coef = 0;
            for (int j = 1; j != sampleCount; j++) {
                double f = (j <= x) ? val*j/x :
                    val*(sampleCount-j)/(sampleCount-x);
                coef += sinTable[j][y]*f;
            }
            coef *= 2.0/sampleCount;

            double ph = phasecoefadj[y]+omega[y]*t; // XXX change elsewhere
            double a = magcoef[y] * phasecoefcos[y];
            double b = magcoef[y] * java.lang.Math.sin(ph);
            if (forceApplied)
                a += forcebasiscoef[y];
            a -= coef;
            double r = java.lang.Math.sqrt(a*a+b*b);
            if (r > 2) r = 2;
            magcoef[y] = r;
            double ph2 = java.lang.Math.atan2(b, a);
            phasecoefadj[y] += ph2-ph;
            forcebasiscoef[y] = coef;
        }
        forceApplied = true;
    }

    void forceAppliedOff() {
        if (!forceApplied)
            return;
        forceApplied = false;
        for (int i = 1; i < maxTerms; i++) {
            double ph = phasecoefadj[i]+omega[i]*t; // XXX change elsewhere
            double a = magcoef[i] * java.lang.Math.cos(ph);
            double b = magcoef[i] * java.lang.Math.sin(ph);
            a += forcebasiscoef[i];
            double r = java.lang.Math.sqrt(a*a+b*b);
            magcoef[i] = r;
            double ph2 = java.lang.Math.atan2(b, a);
            phasecoefadj[i] += ph2-ph;
        }
    }

    void editFuncBow(int xx, int yy) {
        dragging = false;
        bowing = true;
        dragX = xx;
        dragY = yy;
        bowCaught = true;
        cv.repaint(pause);
    }

    void doBow() {
        if (!bowCaught)
            return;
        int panelHeight = getPanelHeight();
        int midy = panelHeight / 2;
        int halfPanel = panelHeight / 2;
        int periodWidth = winSize.width;
        double ymult = .75 * halfPanel;
        int x = dragX * sampleCount / periodWidth;
        double val = (midy - dragY) / ymult;
        if (val < 0)
            val = -val;
        double bowvel = .4;
        if (bowCaught && func[x] > val) {
            bowCaught = false;
            forceAppliedOff();
            return;
        }
        double p = func[x]+bowvel;
        applyForce(x, p);
    }

    double logep2 = 0;
    double logcoef(double x) {
        if (!logCheck.getState())
            return x;
        if (x == 0)
            return x;
        int sign = (x < 0) ? -1 : 1;
        double lg = Math.log(x*sign);
        lg = 1+lg*.1;
        if (lg < 0)
            return 0;
        return sign*lg;
    }

    double unlogcoef(double x) {
        if (!logCheck.getState())
            return x;
        if (x == 0)
            return x;
        int sign = (x < 0) ? -1 : 1;
        double ex = Math.exp((x*sign-1)*10);
        return ex*sign;
    }

    void editFuncPoint(int x, int y) {
        int panelHeight = getPanelHeight();
        int midy = panelHeight / 2;
        int halfPanel = panelHeight / 2;
        int periodWidth = winSize.width;
        double ymult = .75 * halfPanel;
        int lox = x * sampleCount / periodWidth;
        int hix = ((x+1) * sampleCount-1) / periodWidth;
        double val = (midy - y) / ymult;
        if (val > 1)
            val = 1;
        if (val < -1)
            val = -1;
        if (lox < 1)
            lox = 1;
        if (hix >= sampleCount)
            hix = sampleCount-1;
        for (; lox <= hix; lox++) {
            func[lox] = val;
            funci[lox] = 0;
        }
        func[sampleCount] = func[0];
        cv.repaint(pause);
        if (soundCheck.getState() == false)
            transform(false);
    }

    void editFuncPluck(int x, int y) {
        int panelHeight = getPanelHeight();
        int midy = panelHeight / 2;
        int halfPanel = panelHeight / 2;
        int periodWidth = winSize.width;
        double ymult = .75 * halfPanel;
        int ax = x * sampleCount / periodWidth;
        double val = (midy - y) / ymult;
        if (val > 1)
            val = 1;
        if (val < -1)
            val = -1;
        if (ax < 1 || ax >= sampleCount)
            return;
        int i;
        for (i = 0; i <= ax; i++)
            func[i] = val*i/ax;
        int bx = sampleCount-ax;
        for (i = ax+1; i < sampleCount; i++)
            func[i] = val*(sampleCount-i)/bx;
        for (i = 0; i <= sampleCount; i++)
            funci[i] = 0;
        func[sampleCount] = func[0];
        cv.repaint(pause);
        if (soundCheck.getState() == false)
            transform(false);
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) {
        cv.repaint(pause);
    }

    public void componentResized(ComponentEvent e) {
        handleResize();
        cv.repaint(pause);
    }
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == triangleButton) {
            doTriangle();
            cv.repaint();
        }
        if (e.getSource() == sineButton) {
            doSine();
            cv.repaint();
        }
        if (e.getSource() == blankButton) {
            doBlank();
            cv.repaint();
        }
        if (e.getSource() == resonanceButton) {
            forceBar.setValue(20);
            setForce();
        }
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
        System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
        if (e.getSource() == dampingBar || e.getSource() == speedBar)
            setDamping();
        if (e.getSource() == loadBar) {
            setLoadCount();
            updateSound();
        }
        if (e.getSource() == forceBar)
            setForce();
        if (e.getSource() == tensionBar) {
            setTension();
            updateSound();
        }
        cv.repaint(pause);
    }

    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            applet.destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }

    void setTension() {
        int oldTension = tensionBarValue;
        tensionBarValue = tensionBar.getValue();
        double mult = java.lang.Math.sqrt(oldTension/(double) tensionBarValue);
        double roottens = java.lang.Math.sqrt((double) tensionBarValue);
        for (int i = 1; i != maxTerms; i++) {
            magcoef[i] *= mult;
            double oldomegat = omega[i] * t;
            omega[i] = 5*roottens*
                java.lang.Math.sin(i*(3.14159265/(2*(maxTerms+1))));
            double newomegat = omega[i] * t;
            phasecoefadj[i] = (phasecoefadj[i] + oldomegat-newomegat) % (2*pi);
        }
    }

    void setLoadCount() {
        sampleCount = maxTerms = loadBar.getValue();
        step = pi/sampleCount;
        int x, y;
        sinTable = new double[sampleCount+1][maxTerms];
        for (y = 1; y != maxTerms; y++)
            for (x = 0; x != sampleCount+1; x++)
                sinTable[x][y] = java.lang.Math.sin(step*x*y);
        omega = new double[maxTerms];
        int i;
        for (i = 1; i != maxTerms; i++)
            omega[i] = java.lang.Math.sin(i*(3.14159265/(2*(maxTerms+1))));
        double mult = 1/omega[1];
        for (i = 1; i != maxTerms; i++)
            omega[i] *= mult;
        setDamping();
    }

    void setDamping() {
        int i;
        double damper = java.lang.Math.exp(dampingBar.getValue()/40-8);
        if (dampingBar.getValue() <= 2)
            damper = 0;
        dampcoef = -damper;
    }

    public void mouseDragged(MouseEvent e) {
        dragging = true;
        edit(e);
    }
    public void mouseMoved(MouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
            return;
        int x = e.getX();
        int y = e.getY();
        dragX = x; dragY = y;
        int panelHeight = getPanelHeight();
        int oldCoef = selectedCoef;
        selectedCoef = -1;
        selection = 0;
        if (y < panelHeight)
            selection = SEL_FUNC;
        if (y >= magnitudesY && y < magnitudesY+panelHeight) {
            int termWidth = getTermWidth();
            selectedCoef = x/termWidth+1;
            if (selectedCoef >= maxTerms)
                selectedCoef = -1;
            if (selectedCoef != -1)
                selection = SEL_MAG;
        }
        if (selectedCoef != oldCoef)
            cv.repaint(pause);
    }
    public void mouseClicked(MouseEvent e) {
        if (e.getClickCount() == 2 && selectedCoef != -1) {
            int i;
            for (i = 1; i != maxTerms; i++)
                if (selectedCoef != i)
                    magcoef[i] = 0;
            magcoef[selectedCoef] = 1;
            updateSound();
            cv.repaint(pause);
        }
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
        if (!dragging && selectedCoef != -1) {
            selectedCoef = -1;
            cv.repaint(pause);
        }
    }
    public void mousePressed(MouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
            return;
        if (selection == SEL_FUNC)
            getVelocities();
        dragging = true;
        edit(e);
    }
    public void mouseReleased(MouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
            return;
        if (forceApplied || bowing) {
            bowing = bowCaught = false;
            forceAppliedOff();
        } else if (dragging && selection == SEL_FUNC)
            transform(false);
        dragging = false;
        cv.repaint(pause);
    }
    public void itemStateChanged(ItemEvent e) {
        if (e.getItemSelectable() == stoppedCheck) {
            cv.repaint(pause);
            return;
        }
        if (e.getItemSelectable() == forceCheck) {
            forceTimeZero = t;
            cv.repaint(pause);
            forceAppliedOff();
            soundCheck.setState(false);
            return;
        }
        if (e.getItemSelectable() == soundCheck && soundCheck.getState() &&
            playThread == null) {
            playThread = new PlayThread();
            speedBar.setValue(150);
            dampingBar.setValue(100);
            setDamping();
            playThread.start();
        }
        if (e.getItemSelectable() == displayChooser)
            cv.repaint(pause);
    }

    class PlayThread extends Thread {
        public void soundChanged() { changed = true; }
        boolean changed;
        final int rate = 22050;
        public void run() {
            try {
                doRun();
            } catch (Exception e) {
                e.printStackTrace();
            }
            playThread = null;
            soundCheck.setState(false);
        }
        void doRun() {

            // this lovely code is a translation of the following, using
            // reflection, so we can run on JDK 1.1:

            // AudioFormat format = new AudioFormat(rate, 8, 1, true, true);
            // DataLine.Info info =
            //           new DataLine.Info(SourceDataLine.class, format);
            // SourceDataLine line = null;
            // line = (SourceDataLine) AudioSystem.getLine(info);
            // line.open(format, playSampleCount);
            // line.start();

            Object line;
            Method wrmeth = null;
            try {
                Class afclass = Class.forName("javax.sound.sampled.AudioFormat");
                Constructor cstr = afclass.getConstructor(
                    new Class[] { float.class, int.class, int.class,
                                  boolean.class, boolean.class });
                Object format = cstr.newInstance(new Object[]
                    { new Float(rate), new Integer(16), new Integer(1),
                      new Boolean(true), new Boolean(true) });
                Class ifclass = Class.forName("javax.sound.sampled.DataLine$Info");
                Class sdlclass =
                    Class.forName("javax.sound.sampled.SourceDataLine");
                cstr = ifclass.getConstructor(
                    new Class[] { Class.class, afclass });
                Object info = cstr.newInstance(new Object[]
                    { sdlclass, format });
                Class asclass = Class.forName("javax.sound.sampled.AudioSystem");
                Class liclass = Class.forName("javax.sound.sampled.Line$Info");
                Method glmeth = asclass.getMethod("getLine",
                                                  new Class[] { liclass });
                line = glmeth.invoke(null, new Object[] {info} );
                Method opmeth = sdlclass.getMethod("open",
                          new Class[] { afclass, int.class });
                opmeth.invoke(line, new Object[] { format,
                          new Integer(4096) });
                Method stmeth = sdlclass.getMethod("start", null);
                stmeth.invoke(line, null);
                byte b[] = new byte[1];
                wrmeth = sdlclass.getMethod("write",
                          new Class[] { b.getClass(), int.class, int.class });
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }

            int playSampleCount = 16384;
            FFT playFFT = new FFT(playSampleCount);
            double playfunc[] = null;
            byte b[] = new byte[4096];
            int offset = 0;
            int dampCount = 0;
            double mx = .2;

            while (soundCheck.getState() && applet.ogf != null) {
                double damper = dampcoef*1e-2;

                if (playfunc == null || changed) {
                    playfunc = new double[playSampleCount*2];
                    int i;
                    //double bstep = 2*pi*440./rate;
                    //int dfreq0 = 440; // XXX
                    double n = 2*pi*20.0*
                        java.lang.Math.sqrt((double)tensionBarValue);
                    n /= omega[1];
                    changed = false;
                    mx = .2;
                    for (i = 1; i != maxTerms; i++) {
                        int dfreq = (int) (n*omega[i]);
                        if (dfreq >= playSampleCount)
                            break;
                        playfunc[dfreq] = magcoef[i];
                    }
                    playFFT.transform(playfunc, true);
                    for (i = 0; i != playSampleCount; i++) {
                        double dy = playfunc[i*2]*Math.exp(damper*i);
                        if (dy > mx)  mx = dy;
                        if (dy < -mx) mx = -dy;
                    }
                    dampCount = offset = 0;
                }

                double mult = 32767/mx;
                int bl = b.length/2;
                int i;
                for (i = 0; i != bl; i++) {
                    short x = (short) (playfunc[(i+offset)*2]*mult*
                                       Math.exp(damper*dampCount++));
                    b[i*2] = (byte) (x/256);
                    b[i*2+1] = (byte) (x & 255);
                }
                offset += bl;
                if (offset == playfunc.length/2)
                    offset = 0;

                try {
                    wrmeth.invoke(line, new Object[] { b, new Integer(0),
                                                       new Integer(b.length) });
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
}

class FFT {
    double wtabf[];
    double wtabi[];
    int size;
    FFT(int sz) {
        size = sz;
        if ((size & (size-1)) != 0)
            System.out.println("size must be power of two!");
        calcWTable();
    }

    void calcWTable() {
        // calculate table of powers of w
        wtabf = new double[size];
        wtabi = new double[size];
        int i;
        for (i = 0; i != size; i += 2) {
            double pi = 3.1415926535;
            double th = pi*i/size;
            wtabf[i  ] = (double)Math.cos(th);
            wtabf[i+1] = (double)Math.sin(th);
            wtabi[i  ] = wtabf[i];
            wtabi[i+1] = -wtabf[i+1];
        }
    }

    void transform(double data[], boolean inv) {
        int i;
        int j = 0;
        int size2 = size*2;

        if ((size & (size-1)) != 0)
            System.out.println("size must be power of two!");

        // bit-reversal
        double q;
        int bit;
        for (i = 0; i != size2; i += 2) {
            if (i > j) {
                q = data[i]; data[i] = data[j]; data[j] = q;
                q = data[i+1]; data[i+1] = data[j+1]; data[j+1] = q;
            }
            // increment j by one, from the left side (bit-reversed)
            bit = size;
            while ((bit & j) != 0) {
                j &= ~bit;
                bit >>= 1;
            }
            j |= bit;
        }

        // amount to skip through w table
        int tabskip = size << 1;
        double wtab[] = (inv) ? wtabi : wtabf;

        int skip1, skip2, ix, j2;
        double wr, wi, d1r, d1i, d2r, d2i, d2wr, d2wi;

        // unroll the first iteration of the main loop
        for (i = 0; i != size2; i += 4) {
            d1r = data[i];
            d1i = data[i+1];
            d2r = data[i+2];
            d2i = data[i+3];
            data[i  ] = d1r+d2r;
            data[i+1] = d1i+d2i;
            data[i+2] = d1r-d2r;
            data[i+3] = d1i-d2i;
        }
        tabskip >>= 1;

        // unroll the second iteration of the main loop
        int imult = (inv) ? -1 : 1;
        for (i = 0; i != size2; i += 8) {
            d1r = data[i];
            d1i = data[i+1];
            d2r = data[i+4];
            d2i = data[i+5];
            data[i  ] = d1r+d2r;
            data[i+1] = d1i+d2i;
            data[i+4] = d1r-d2r;
            data[i+5] = d1i-d2i;
            d1r = data[i+2];
            d1i = data[i+3];
            d2r = data[i+6]*imult;
            d2i = data[i+7]*imult;
            data[i+2] = d1r-d2i;
            data[i+3] = d1i+d2r;
            data[i+6] = d1r+d2i;
            data[i+7] = d1i-d2r;
        }
        tabskip >>= 1;

        for (skip1 = 16; skip1 <= size2; skip1 <<= 1) {
            // skip2 = length of subarrays we are combining
            // skip1 = length of subarray after combination
            skip2 = skip1 >> 1;
            tabskip >>= 1;
            for (i = 0; i != 1000; i++);
            // for each subarray
            for (i = 0; i < size2; i += skip1) {
                ix = 0;
                // for each pair of complex numbers (one in each subarray)
                for (j = i; j != i+skip2; j += 2, ix += tabskip) {
                    wr = wtab[ix];
                    wi = wtab[ix+1];
                    d1r = data[j];
                    d1i = data[j+1];
                    j2 = j+skip2;
                    d2r = data[j2];
                    d2i = data[j2+1];
                    d2wr = d2r*wr - d2i*wi;
                    d2wi = d2r*wi + d2i*wr;
                    data[j]    = d1r+d2wr;
                    data[j+1]  = d1i+d2wi;
                    data[j2  ] = d1r-d2wr;
                    data[j2+1] = d1i-d2wi;
                }
            }
        }
    }

}

Author: Matt Dailis

Created: 2020-04-20 Mon 21:45

Validate