package sysy;

import processing.core.*;
//import processing.xml.*;

import ddf.minim.signals.*;
import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
//import processing.opengl.*;
import java.lang.reflect.*;
///import java.awt.*;
import promidi.*;

//import java.applet.*;
import java.awt.*;
//import java.awt.image.*;
//import java.awt.event.*;
//import java.io.*;
//import java.net.*;
//import java.util.zip.*;
//import java.util.regex.*;
//import java.text.*;
import java.util.*;

// TODO: wenn man ein kabel auf einen belgten anschluss l\u00f6sl\u00e4sst -> nullpointer!
// TODO: urg!
/**
 *
 * @author benediktseidl
 */
public class sysy extends PApplet {

    sysy sysy;
    InputRack i1;
    Drag drag;
    PFont font;
    Wires wires;
    NewRack newRack;
    ArrayList midiController;
    ArrayList midiNotes;
    Minim minim;
    AudioOutput aOut;
    MidiIO midiIO;
    int notenan = 0;
    ArrayList<Integer> midiar;

    /**
     * hier startet das programm
     * @param args
     */
    static public void main(String args[]) {
        PApplet.main(new String[]{"--bgcolor=#FFFFFF", "sysy.sysy"});
    }

    /**
     * Hier startet es normalerweise von processing aus
     */
    @Override
    public void setup() {
        sysy = this;
        size(1000, 720); //, OPENGL);
        // hint(ENABLE_OPENGL_4X_SMOOTH);
        frameRate(40);

        wires = new Wires();
        drag = new Drag();
        newRack = new NewRack();

        midiar = new ArrayList<Integer>();
        midiar.add(333);

        minim = new Minim(this);
        midiController = new ArrayList();
        midiNotes = new ArrayList();

       // midiIO = MidiIO.getInstance(this); //get an instance of midiIO
       // midiIO.printDevices();         // print a list of all devices
        // TODO: check ob ausgang da ist, sonst nicht aktivieren!
     //   if (midiIO.numberOfInputDevices() > 2) {
          //    midiIO.openInput(1, 0);
       // }


        font = loadFont("/Users/benediktseidl/Code/NetBeansProjects/testingp5/src/sysy/font.vlw");
        textFont(font, 12);
    }

    //#########################
    //## von hier wird alles weitergeleitet
    //#########################
    /**
     * zeichnet den inhalt!
     */
    @Override
    public void draw() {
        background(50);
        newRack.draw();
        drag.draw();
        if (wires.p != null) {
            wires.mmouseDragged(mouseX, mouseY);
        }
    }

    /**
     *
     */
    @Override
    public void mousePressed() {
        drag.click(mouseX, mouseY);
        wires.mmousePressed(mouseX, mouseY);
        newRack.click();
    }

    /**
     *
     */
    @Override
    public void mouseDragged() {
        drag.mouseDr(mouseX, mouseY);
    }

    /**
     *
     */
    @Override
    public void mouseReleased() {
        drag.mouseUp();
        wires.mmouseReleased();
    }

    @Override
    public void stop() {
        minim.stop();// always close Minim audio classes when you are done with them
        super.stop();
    }

    //#########################
    //## hier sind die groben sachen
    //#########################
    /**
     *
     */
    public class Drag {

        ArrayList liste;
        int ox, oy;
        int fx, fy;
        dragable tr;

        Drag() {
            liste = new ArrayList();
        }

        /**
         *
         * @param o
         */
        public void add(drawable o) {
            liste.add(o);
            if (o.getClass().getSimpleName().equals("InputRack")) {
                liste.add(((InputRack) o).inputRect);
            } else if (o.getClass().getSimpleName().equals("SquareInput")) {
                liste.add(((SquareInput) o).squareInputRect);
            }
        }

        /**
         *
         */
        public void draw() {
            for (int i = liste.size() - 1; i >= 0; i--) {
                ((drawable) liste.get(i)).draw();
            }
        }

        /**
         *
         * @param x
         * @param y
         */
        public void click(int x, int y) {
            tr = null;
            ox = x;
            oy = y;
            fx = 0;
            fy = 0;
            for (int i = liste.size() - 1; i >= 0; i--) {
                if (((dragable) liste.get(i)).getDrag().contains(x, y)) {
                    tr = ((dragable) liste.get(i));
                    mouseDr(x, y);
                }

                if (((closeable) liste.get(i)).getClose().contains(x, y) && keyPressed) {
                    boolean exxit = false;
                    // zuerst schauen ob nicht ein wire hier her geht!

                    Rack myRack = ((Rack) liste.get(i));

                    for (int k = myRack.options.size() - 1; k >= 0; k--) {
                        Option myOption = ((Option) myRack.options.get(k));

                        if (myOption.wireTo != null) {
                            exxit = true;
                        }

                        for (int j = wires.wires.size() - 1; j >= 0; j--) {
                            if (((Option) wires.wires.get(j)).wireTo == myOption) {
                                exxit = true;
                            }
                        }
                    }

                    if (exxit == false) {
                        myRack.kill();
                        liste.remove(myRack);
                    }

                }

            }
        }

        /**
         *
         * @param x
         * @param y
         */
        public void mouseDr(int x, int y) {
            if (tr != null) {
                if (tr.getClass().getSimpleName().equals("InputRect")) {
                    ((InputRect) tr).addValue((float) (ox - x));
                } else if (tr.getClass().getSimpleName().equals("SquareInputRect")) {
                    ((SquareInputRect) tr).setLoc((float) (x), (float) (y));
                } else {
                    tr.x = tr.x - (ox - x);
                    tr.y = tr.y - (oy - y);
                }
            }
            ox = x;
            oy = y;
        }

        /**
         *
         */
        public void mouseUp() {
            if (tr != null) {
                if (tr.getClass().getSimpleName().equals("SquareInputRect")) {
                    ((SquareInputRect) tr).parent.o3.setValue(0);
                }

                if (tr.x < 150) {
                    tr.x = 170;
                }

                if (tr.y < 0) {
                    tr.y = 0;
                }

                if (tr.y > height - tr.h) {
                    tr.y = height - tr.h;
                }


                if (tr.x + tr.h > width) {
                    tr.x = width - tr.h;
                }

            }

            tr = null;
        }
    }

    /**
     *
     */
    public class Option {

        float value;
        float min, max;
        boolean inout = false;
        int h, ry;
        String name = "";
        Rectangle rec;
        Option wireTo;
        Rack parent;
        boolean sound = false;
        boolean wire;
        boolean clip;

        Option() {
            //bisschen unsch\u00f6n hier,.. wegen dem plug :-/
        }

        Option(String name, float min, float max, float value, int h, boolean inout) {
            this(name, min, max, value, h, inout, false);
        }

        Option(String name, float min, float max, float value, int h, boolean inout, boolean sound) {
            this.name = name;
            this.min = min;
            this.max = max;
            this.value = value;
            this.h = h;
            this.inout = inout;
            this.sound = sound;
        }

        /**
         *
         * @param nV
         */
        public void setValue(float nV) {

            if (nV < min) {
                value = min;
                clip = true;
            } else if (nV > max) {
                value = max;
                clip = true;
            } else {
                value = nV;
                clip = false;
            }

            if (wireTo != null) {
                wireTo.setValue(value);
            }
        }

        /**
         *
         */
        public void draw() {
            noStroke();
            fill(0, 40); // hintergrund f\u00fcr volle breite
            rect(parent.x, parent.y + ry, parent.w, h);

            int xoff = 60;
            if (sound == true) {
                xoff = 5;
            }

            fill(128); // beschreibungstext
            text(name, parent.x + xoff, parent.y + ry + 15);

            if (!sound) {
                fill(0xff9b6700); // hintergrund?! vom balken!
                rect(parent.x + 5, parent.y + ry + (h / 2) - 5, 50, 10);

                if (!clip) {
                    fill(0xffffaa00); // inhalt!
                } else {
                    fill(0xffff3300);
                }
                rect(parent.x + 5, parent.y + ry + (h / 2) - 5, map(value, min, max, 0, 50), 10);

                fill(255, 100); // nullllpunkt!
                rect(parent.x + 5 + map(0, min, max, 0, 50) - 1, parent.y + ry + (h / 2) - 5, 2, 10);
            }

            if (!wire) {
                fill(80);
                rect(this.getWireRect().x, this.getWireRect().y, this.getWireRect().width, this.getWireRect().height);
            }
// else{
            //fill(255,255,0);
            //     rect(this.getWireRect().x, this.getWireRect().y, this.getWireRect().width, this.getWireRect().height);
            //}
            wire = false;

            if (wireTo != null) {
                (new Wire()).wire(this.parent.x + this.parent.w, this.parent.y + this.ry + (this.h / 2), wireTo.parent.x, wireTo.parent.y + wireTo.ry + (wireTo.h / 2), this.sound);
                wireTo.wire = true;
            }

        }

        /**
         *
         * @return
         */
        public Rectangle getWireRect() {
            if (inout) {
                return new Rectangle(parent.x + parent.w, parent.y + ry, 20, 20);
            } else {
                return new Rectangle(parent.x - 20, parent.y + ry, 20, 20);
            }
        }
    }

    /**
     * Grundstruktur für alle Module.
     */
    public class Rack extends dragable implements drawable, closeable {

        // int x,y,w,h;
        String name;
        ArrayList options;
        int tc;

        //Rectangle drag
        Rack() {
            // etwas dreckig,.. wegen den doofen wires,..
        }

        /**
         *
         * @param x
         * @param y
         * @param name
         * @param tc
         */
        public Rack(int x, int y, String name, int tc) {
            w = 150;
            h = 30;
            this.x = x;
            this.y = y;
            this.name = name;
            options = new ArrayList();
            this.tc = tc;
        }

        Rack(int x, int y, String name) {
            this(x, y, name, color(55, 0, 0));
        }

        /**
         *
         */
        public void kill() {
        }

        /**
         *
         * @return
         */
        @Override
        public Rectangle getDrag() {
            return new Rectangle(x, y, w, 20);
        }

        /**
         *
         * @param o
         */
        public void addOption(Option o) {
            options.add(o);
            o.ry = h;
            o.parent = this;
            h += o.h + 5;
            //h\u00f6he, x,y etc!

            wires.add(o);

            // das einfachse wird sein, wenn man das rack mit an die option \u00fcbergibt, sonst muss man x y immer beim bewegen neu berechnen!
        }

        /**
         *
         * @return
         */
        public Rectangle getClose() {
            return new Rectangle(this.getDrag().x + this.getDrag().width - 20, this.getDrag().y, 20, 20);
        }

        /**
         *
         */
        public void draw() {


            noStroke();
            fill(tc);
            rect(x, y, w, h);
            fill(0, 50);
            rect(this.getDrag().x, this.getDrag().y, this.getDrag().width, this.getDrag().height);
            fill(255);
            text(name, x + 5, y + 15);
            fill(0, 100);

            int closex = this.getClose().x;
            int closey = this.getClose().y;

            rect(closex, closey, 20, 20);
            strokeWeight(2);
            stroke(200, 100);
            line(closex + 5, closey + 5, closex + 20 - 5, closey + 20 - 5);
            line(closex + 5, closey + 20 - 5, closex + 20 - 5, closey + 5);

            boolean debug = false;

            if (debug) {
                println("[" + this.name + "]");
            }

            for (int i = options.size() - 1; i >= 0; i--) {
                ((Option) options.get(i)).draw();

                if (debug) {
                    print(" " + ((Option) options.get(i)).name);
                    if (((Option) options.get(i)).wireTo != null) {
                        print(" -> " + ((Option) options.get(i)).wireTo.name);
                    }
                    println("");
                }
            }

            // hier auch die options zeichnen!
        }
    }

    /**
     *
     */
    public interface drawable {

        /**
         *
         */
        public void draw();
    }

    /**
     *
     */
    public interface closeable {

        /**
         *
         * @return
         */
        public Rectangle getClose();
    }

    /**
     *
     */
    public class dragable {

        boolean mini = false;
        int x, y, w, h;

        /**
         *
         * @return
         */
        public Rectangle getDrag() {
            return null;
        }
    }

    //###################################
    //## MIDI STUFF!
    //###################################
    final static class Midi {

        public static float toFreq(int i) {
            float a = 440;
            return ((a / 32.0f) * (pow(2, ((i - 9.0f) / 12.0f))));
            /*   http://jedi.ks.uiuc.edu/~johns/links/music/notefreq.htm
            DIM MIDI(127)
            A=440
            FOR x = 0 to 127
            MIDI(x) = (A / 32) * (2 ^ ((x - 9) / 12))
            NEXT x
             */

        }
    }

    /**
     *
     * @param note
     * @param deviceNumber
     * @param midiChannel
     */
    public void noteOn(Note note, int deviceNumber, int midiChannel) {
        int vel = note.getVelocity();
        int pit = note.getPitch();

        if (vel >= 0) {

            println("note an:" + vel + " " + pit + " - " + notenan);
            println("####" + Midi.toFreq(pit));


            notenan += 1;

            midiar.add(pit); //

            for (int b : midiar) {

                println("*** " + b);
            }

            // TO DO: array anlegen, dass das für jede note gemacht wird und dann geschaut wird ob irgend eine noch an ist!
            // TO DO: naja,.. urg! wäre besser mit einer arraylist, in der man die gedrücke nacheinander ein und austrägt,
            // dann wäre das besser mit dem loslassen der noten und den gepressten tasten
            // TODO: ja, nur funktioniert das so irgendwie nicht,..

            for (int i = midiNotes.size() - 1; i >= 0; i--) {
                ((NOption) midiNotes.get(i)).pressed.setValue(1);
                ((NOption) midiNotes.get(i)).setValue(Midi.toFreq(pit) / 10000);

            }

        }
    }

    /**
     *
     * @param note
     * @param deviceNumber
     * @param midiChannel
     */
    public void noteOff(Note note, int deviceNumber, int midiChannel) {
        int pit = note.getPitch();

        println("note aus:" + 0 + " " + pit + " - " + notenan);
        println("####" + Midi.toFreq(pit));


        //notenan -= 2; // TODO: WHATTT? das kann doch nicht sein? jedes mal beim loslassen wird noch mal note an geschickt?! hmmmmm

        try {
            for (int i = midiar.size() - 1; i >= 0; i--) {
                if (midiar.get(i).equals(pit)) {
                    println("remove " + i + " " + midiar.get(i));
                    midiar.remove(i);
                } else {
                    println("da     " + i + " " + midiar.get(i));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        boolean pressed = true;

        if (midiar.size() <= 1) {
            pressed = false;
        }

        for (int i = midiNotes.size() - 1; i >= 0; i--) {
            if (pressed == false) {
                ((NOption) midiNotes.get(i)).pressed.setValue(0);
            }
            if (midiar.size() > 1) {
                ((NOption) midiNotes.get(i)).setValue(Midi.toFreq(midiar.get(midiar.size() - 1)) / 10000);
            }
        }

        int j = 0;
        for (int b : midiar) {
            println("***** " + j++ + " " + b);
        }
    }

    /**
     *
     * @param controller
     * @param deviceNumber
     * @param midiChannel
     */
    public void controllerIn(promidi.Controller controller, int deviceNumber, int midiChannel) {
        println("controllerin");
        int num = controller.getNumber();
        int val = controller.getValue();
        for (int i = midiController.size() - 1; i >= 0; i--) {
            ((COption) midiController.get(i)).setValue(num, val);
        }
    }

    /**
     *
     * @param programChange
     * @param deviceNumber
     * @param midiChannel
     */
    public void programChange(ProgramChange programChange, int deviceNumber, int midiChannel) {
        int num = programChange.getNumber();
        println("programm cahnge! " + num);
    }

    //###################################
    //## sound sachen
    //###################################
    /**
     *
     */
    public class SoundOption extends Option {

        Oscillator signal; //signal;

        /**
         *
         */
        public SoundOption() {
            super("Output", 0, 0, 0, 20, true);
            this.sound = true;
        }

        /**
         *
         * @param value
         */
        public void setFreq(float value) {
            signal.setFreq(value * 440);
        }

        /**
         *
         * @param value
         */
        public void setAmp(float value) {
            signal.setAmp(value);
        }
    }

    /**
     * Grundmodul für die Zusätzliche Sound-Kabel verlegerei
     */
    public class SoundRack extends Rack {

        SoundRack start;
        SoundRack next;

        /**
         *
         * @param x
         * @param y
         * @param name
         */
        public SoundRack(int x, int y, String name) {
            super(x, y, name, color(0, 0, 66));
        }

        /**
         *
         * @param start
         * @param next
         */
        public void addRack(SoundRack start, SoundRack next) {
            this.next = next;
            if (start != null) {
                this.setStart(start);
            }
        }

        /**
         *
         * @return
         */
        public SoundRack getLastRack() {
            if (next != null) {
                return next.getLastRack();
            } else {
                return null;
            }
        }

        /**
         *
         * @param start
         */
        public void setStart(SoundRack start) {
            this.start = start;
            if (next != null) {
                next.setStart(start);
            }
        }

        /**
         *
         */
        @Override
        public void kill() {
            if (getLastRack() != null) {
                getLastRack().kill();
            }
        }

        /*  void draw()
        {
        super.draw();
        this.getinfo();
        }*/
        /**
         *
         */
        public void getinfo() {

            println("##" + this.name);

            if (start != null) {
                println(" start: " + start.name);
            } else {
                println(" start: null");
            }

            if (this.getLastRack() != null) {
                println(" ende: " + this.getLastRack().name);
            } else {
                println(" ende: null");
            }

            if (this.next != null) {
                println(" next: " + this.next.name);
            } else {
                println(" next: null");
            }
        }
    }

    /**
     *  Grundlage für alle Effekt-Module
     * haben einen sound-ein und ausgang
     */
    public abstract class EffectRack extends SoundRack {

        Option output;
        Option input;
        AudioEffect ae;

        /**
         *
         * @param x
         * @param y
         * @param name
         */
        public EffectRack(int x, int y, String name) {
            super(x, y, name);
            output = new Option("Output", 0, 0, 0, 20, true, true);
            input = new Option("Input", 0, 0, 0, 20, false, true);
            this.addOption(output);
            this.addOption(input);
        }
    }

    /**
     *
     */
    public class SignalRack extends SoundRack {

        AudioSignal signal;

        /**
         *
         * @param x
         * @param y
         * @param name
         */
        public SignalRack(int x, int y, String name) {
            super(x, y, name);
            this.start = this;
        }
    }

    //###################################
    //## Kabel
    //###################################
    /**
     *
     */
    public class Wire {

        /**
         *
         */
        public Wire() {
        }

        /**
         *
         * @param x1
         * @param y1
         * @param x2
         * @param y2
         */
        public void wire(int x1, int y1, int x2, int y2) {
            wire(x1, y1, x2, y2, false);
        }

        /**
         *
         * @param x1
         * @param y1
         * @param x2
         * @param y2
         * @param sound
         */
        public void wire(int x1, int y1, int x2, int y2, boolean sound) {

            int tc;
            int tcd;

            strokeWeight(4);
            if (sound) {
                tc = color(30);
                tcd = color(0);
            } else {
                tc = color(0, 90, 0);
                tcd = color(0, 50, 0);
            }
            stroke(tc);

            if (x1 + 40 <= x2) {

                noFill();
                beginShape();
                vertex(x1 + 20, y1);
                vertex(x1 + (x2 - x1) * 0.5f, y1);
                vertex(x1 + (x2 - x1) * 0.5f, y2);
                vertex(x2 - 20, y2);
                endShape();

                fill(tc);
                noStroke();
                ellipse(x1 + (x2 - x1) * 0.5f, y1, 4, 4);
                ellipse(x1 + (x2 - x1) * 0.5f, y2, 4, 4);
            } else {
                int xx = 40;
                if ((x1 - x2) > 0 && (x1 - x2) < 20) {
                    xx = 20 + (x1 - x2);
                } else if ((x1 - x2) < 20) {
                    xx = 20;
                }

                noFill();
                beginShape();
                vertex(x1 + 20, y1);
                vertex(x1 + xx, y1);
                vertex(x1 + xx, y1 + (y2 - y1) * 0.5f);
                vertex(x2 - xx, y1 + (y2 - y1) * 0.5f);
                vertex(x2 - xx, y2);
                vertex(x2 - 20, y2);
                endShape();

                fill(tc);
                noStroke();
                ellipse(x1 + xx, y1, 4, 4);
                ellipse(x1 + xx, y1 + (y2 - y1) * 0.5f, 4, 4);
                ellipse(x2 - xx, y1 + (y2 - y1) * 0.5f, 4, 4);
                ellipse(x2 - xx, y2, 4, 4);
                noStroke();
            }

            fill(tcd);
            rect(x1, y1 - 10, 20, 20);
            rect(x2 - 20, y2 - 10, 20, 20);

        }
    }

    /**
     *
     */
    public class Wires {

        ArrayList wires;
        Plug p;
        Option to;
        Option from;

        Wires() {
            wires = new ArrayList();
        }

        /**
         *
         * @param o
         */
        public void add(Option o) {
            wires.add(o);
        }

        /**
         *
         * @param x
         * @param y
         */
        public void mmousePressed(int x, int y) {
            for (int i = wires.size() - 1; i >= 0; i--) {
                if (((Option) wires.get(i)).getWireRect().contains(x, y)) { // eines wurde getroffen


                    Option o = ((Option) wires.get(i));

                    int k;
                    if (o.inout) {
                        k = +20;
                    } else {
                        k = 0;
                    }

                    p = new Plug(o.getWireRect().x + k, o.getWireRect().y);
                    p.inout = o.inout;
                    p.sound = o.sound;
                    p.urg = o;




                    if (o.wireTo != null) {

                        //  println(o.name); --> o steht f\u00fcr das von dem das kabel kommt.

                        if (o.sound == true) {
                            // println("hier!");
                            // schei\u00dfe,.. hier kommt es auch an, wenn es von der anderen seite kommt!
                            // fuck fuck fuck,.. wenn ich das jetzt noch reinpfusche, bekomme ich probleme mit dem n\u00e4chsten schritt, den effekten,.. oder?!
                            // ich glaube das so und so?!
                            //--//    ((Out)o.wireTo).out.removeSignal(((SinusSound)o).signal); // das sinus sound durch eine klasse die von options erbt und das
                            // signal implementiert ersetzen!
                        }

                        p.wireTo = o.wireTo;
                        p.wireFrom = p;
                        o.wireTo = null;
                        p.inout = !o.inout;
                        //   println ("ZWO!!!!!! // gerade wurde ein wire getroffen!");

                        // das killen von audio-sachen schon mein klicken auf das wire!
                        // p ist der dummy

                        if (p.sound == true) {
                            ((SoundRack) o.parent).kill();
                        }


                        p.x = p.x - 20;

                        break;
                    }




                    boolean replace = false;

                    for (int j = wires.size() - 1; j >= 0; j--) {
                        if (((Option) wires.get(j)).wireTo == o) {
                            from = ((Option) wires.get(j));
                            replace = true;
                            break;
                        }
                    }

                    if (replace) {
                        p.inout = !o.inout;
                        from.wireTo = p;
                        p.wireFrom = from;

                        p.x = p.x + 20;

                        // println ("EINNNSNSNNSN!!!!!!//gerade wurder die maus gedrückt und ein wire getroffen");



                        // das killen von audio-sachen schon mein klicken auf das wire!
                        // p ist der dummy

                        if (p.sound == true) {
                            ((SoundRack) o.parent).kill();
                        }



                        break;
                    }

                    if (o.inout == true) {
                        // true bedeutete dass die option einen ausgang hat.
                        // d.h. unser plug war nur das ziel.
                        o.wireTo = p;
                        p.wireFrom = o;
                    } else {
                        // d.h. von unserem plug geht das kabel aus,
                        // d.h. dort hin wo das kabel gezogen wird muss das wireto gesetzt werden!
                        p.wireTo = o;
                        p.wireFrom = p;
                    }



                }
            }
        }

        /**
         *
         * @param x
         * @param y
         */
        public void mmouseDragged(int x, int y) {
            if (p != null) {

                for (int i = wires.size() - 1; i >= 0; i--) {
                    Option o = ((Option) wires.get(i));
                    if (o.inout != p.inout && o.getWireRect().intersects(p.getWireRect()) && o.sound == p.sound) {
                        //      println(o.name + " " + p.name );

                        boolean exxit = false;
                        for (int j = wires.size() - 1; j >= 0; j--) {
                            if (((Option) wires.get(j)).wireTo == o) {
                                exxit = true;
                                break;
                            }
                        }
                        if ((exxit != true && o.wireTo == null)) {
                            // && !(p.wireTo == null && p.wireFrom.parent == o.parent) || (p.wireFrom.parent.name == null && p.wireTo.parent == o.parent)

                            //          println(p.wireTo.parent.name);
                            //                  println(p.wireFrom.parent.name);
                            strokeWeight(1);
                            stroke(255);
                            fill(255, 100);
                            rect(o.getWireRect().x - 10, o.getWireRect().y - 10, o.getWireRect().width + 20, o.getWireRect().height + 20);
                            to = o;
                            break;
                        }

                    } else {
                        to = null;
                    }

                }
                p.x = p.x + mouseX - pmouseX;
                p.y = p.y + mouseY - pmouseY;


                p.draw();


                //      if (to != null)
                //        println(to.name);
                //      else
                //        println(null);
            }
        }

        /**
         *
         */
        public void mmouseReleased() {

            if (p != null) {
                if (p.inout == true) {
                    p.wireFrom.wireTo = to;
                    if (p.wireFrom != null) {
                        p.wireFrom.setValue(p.wireFrom.value);

                        // hier die soundgeschichten // vom generator zum ausgang
                        if (p.wireFrom.wireTo != null) {
                            if (p.wireFrom.wireTo.sound == true) {
                                SoundRack generator = ((SoundRack) p.wireFrom.parent);
                                generator.addRack(generator.start, ((SoundRack) p.wireFrom.wireTo.parent));
                            }
                        } else { // verbindung wird unterbrochen auf die empf\u00e4nger seite wurde geklickt!
                            if ((p.wireFrom.parent).tc == color(0, 0, 66)) // urgs! das ist nicht sch\u00f6n, und k\u00f6nnte zu ziemlichen problemen f\u00fchren.
                            {

                                SoundRack generator = ((SoundRack) p.wireFrom.parent);
                                //   println(generator.name);
                                generator.kill();
                                generator.addRack(generator.start, null);

                            }
                        }


                    }
                    p = null;
                } else {
                    if (to != null) {
                        to.wireTo = p.wireTo;
                        to.setValue(to.value);

                        // hier die soundgeschichten
                        if (to.wireTo != null && to.wireTo.sound == true) { // hinzuf\u00fcgen!
                            SoundRack generator = ((SoundRack) to.parent);
                            generator.addRack(generator.start, ((SoundRack) to.wireTo.parent));

                        }

                    } else { // verbindung wird unterbrochen, auf die generator seite wurde geklickt

                        if (p.wireTo.sound == true) {
                            // println("wtf!?" + p.urg.parent.name);
                            SoundRack generator = ((SoundRack) p.urg.parent); // FUCKCKCKKKK!!!

                            //println(generator.name);
                            generator.kill();

                            generator.addRack(generator.start, null);
                        }


                    }
                    p = null;
                }
            }
        }
    }

    /**
     *
     */
    public class Plug extends Option {

        int x, y;
//  Option wireTo;
        Option wireFrom;
        Option urg;

        /**
         *
         * @param x
         * @param y
         */
        public Plug(int x, int y) {
            super();
            this.x = x;
            this.y = y + 10;
            h = 0;
            parent = new NoRack();
            parent.x = this.x;
            parent.y = this.y;
            // parent.ry = 0;
        }

        /**
         *
         */
        @Override
        public void draw() {
            parent.x = x;
            parent.y = y;

            // fill(255);
            // rect(this.getWireRect().x, this.getWireRect().y, this.getWireRect().width, this.getWireRect().height);

            if (wireTo != null) {
                (new Wire()).wire(this.parent.x + this.parent.w, this.parent.y + this.ry + (this.h / 2), wireTo.parent.x, wireTo.parent.y + wireTo.ry + (wireTo.h / 2), this.sound);

            }
        }

        /**
         *
         * @return
         */
        @Override
        public Rectangle getWireRect() {
            if (inout) {
                return new Rectangle(parent.x - 20, parent.y - 10, 20, 20);
            } else {
                return new Rectangle(parent.x, parent.y - 10, 20, 20);
            }
        }
    }

    /**
     *
     */
    public class NoRack extends Rack {

        NoRack() {
            super();
        }
        int ry;
    }

    //###################################
    //## Seiten leiste und erzeugung neuer Module
    //###################################
    /**
     *
     */
    public class NewRack {

        ArrayList racks;

        /**
         *
         */
        public NewRack() {
            racks = new ArrayList();
            int x = -20;

            racks.add(new miniRack(5, x += 25, "Input", "sysy.sysy$InputRack", false));
            racks.add(new miniRack(5, x += 25, "Square Input", "sysy.sysy$SquareInput", false));
            x += 5;
            racks.add(new miniRack(5, x += 25, "Inverter [-1,1]", "sysy.sysy$Inverter", false));
            racks.add(new miniRack(5, x += 25, "Inverter [0,1]", "sysy.sysy$Inverter2", false));

            racks.add(new miniRack(5, x += 25, "Sinus", "sysy.sysy$SinusModul", false));//racks.add(new miniSinm(5, x += 25));
            racks.add(new miniRack(5, x += 25, "Splitter", "sysy.sysy$Splitter", false));//racks.add(new miniSplit(5, x += 25));
            racks.add(new miniRack(5, x += 25, "Map [-1,1]>[0,1]", "sysy.sysy$Map1", false));//racks.add(new miniMap1(5, x += 25));
            racks.add(new miniRack(5, x += 25, "Exp", "sysy.sysy$Exp", false));//racks.add(new miniExp(5, x += 25));
            racks.add(new miniRack(5, x += 25, "Display", "sysy.sysy$NFDisplay", false));//racks.add(new miniExp(5, x += 25));

            x += 5;
            racks.add(new miniRack(5, x += 25, "Multiplier", "sysy.sysy$Multiplier", false));//racks.add(new miniMu(5, x += 25));
            racks.add(new miniRack(5, x += 25, "Adder", "sysy.sysy$Adder", false));//racks.add(new miniMu(5, x += 25));
            x += 5;

            racks.add(new miniRack(5, x += 25, "Ramp", "sysy.sysy$Ramp", false));//racks.add(new miniRamp(5, x += 25)); // miniMu
            racks.add(new miniRack(5, x += 25, "Sample & Hold", "sysy.sysy$SandHModul", false));//racks.add(new miniRamp(5, x += 25)); // miniMu
            racks.add(new miniRack(5, x += 25, "Glider", "sysy.sysy$Glider", false));//racks.add(new miniRamp(5, x += 25)); // miniMu

            x += 5;
            racks.add(new miniRack(5, x += 25, "Midi", "sysy.sysy$MidiRack", false));//racks.add(new miniMidi(5, x += 25));
            x += 5;
            racks.add(new miniRack(5, x += 25, "Lowpass", "sysy.sysy$LowPassRack", true));//racks.add(new miniLowPass(5, x += 25));
            x += 5;
            racks.add(new miniRack(5, x += 25, "Sinus", "sysy.sysy$SinusRack", true));//racks.add(new miniSin(5, x += 25));
            racks.add(new miniRack(5, x += 25, "Saw", "sysy.sysy$SawRack", true));// racks.add(new miniSaw(5, x += 25));
            racks.add(new miniRack(5, x += 25, "White Noise", "sysy.sysy$NoiseRack", true));// racks.add(new miniNoise(5, x += 25));
            x += 5;
            racks.add(new miniRack(5, x += 25, "Display", "sysy.sysy$SDisplay", true));// racks.add(new miniSD(5, x += 25));
            racks.add(new miniRack(5, x += 25, "FFT Display", "sysy.sysy$FFTDisplay", true));//racks.add(new miniFFTD(5, x += 25));
            x += 5;
            racks.add(new miniRack(5, x += 25, "Output", "sysy.sysy$OutRack", true));//racks.add(new miniOut(5, x += 25));*/


        }

        /**
         *
         * @param name
         * @param x
         * @param y
         */
        public void createRack(String name, int x, int y) {
        }

        /**
         *
         */
        public void draw() {
            noStroke();
            fill(40);
            rect(0, 0, 160, height);

            for (int i = racks.size() - 1; i >= 0; i--) {
                ((drawable) racks.get(i)).draw();
            }

        }

        /**
         *
         */
        public void click() {
            for (int i = racks.size() - 1; i >= 0; i--) {
                miniRack a = ((miniRack) racks.get(i));
                if ((new Rectangle(a.x, a.y, 150, 20)).contains(mouseX, mouseY)) {
                    a.command();
                }
            }
            //println();
        }
    }

    /**
     *
     */
    public class miniRack implements drawable {

        int x, y;
        String name;
        String className;
        boolean sound;

        miniRack(int x, int y, String name, String className, boolean sound) {
            this.name = name;
            this.x = x;
            this.y = y;
            this.sound = sound;
            this.className = className;
        }

        /**
         *
         */
        public void draw() {
            noStroke();
            if (sound) {
                fill(0, 0, 66);
            } else {
                fill(55, 0, 0);
            }
            rect(x, y, 150, 20);
            fill(0, 50);
            rect(x, y, 150, 20);
            fill(255);
            text(name, x + 5, y + 15);
        }

        /**
         *
         */
        public void command() {

            //  println("rest! " + className);

            try {

                // nice try :-/ Class rectangleDefinition = Class.forName("sysy.sysy$SquareInput", true, new ClassLoader(){});//forName("sysy.sysy$SquareInput");//(className);
                Class rectangleDefinition = Class.forName(className);//(className);
                //  println("class.forname: ok");
                Constructor[] constructors = rectangleDefinition.getConstructors();
                /*   println("construktor länge " + constructors.length);
                for (int i = 0; i < constructors.length; i++) {
                println("--" + constructors[i].toString());
                }*/
                Constructor constructor = rectangleDefinition.getConstructor(new Class[]{sysy.class, int.class, int.class});
                //println("contrusktor: ork");
                Object object = constructor.newInstance(new Object[]{sysy,
                            new Integer(x), new Integer(y)});
                //System.out.println("Object: " + object.toString());


                //Inverter2 asd = new Inverter2(mouseX, mouseY);
                drag.add((drawable) object);
                drag.tr = ((dragable) object);

            } catch (ClassNotFoundException e) {
                System.out.println(e);
            } catch (NoSuchMethodException e) {
                System.out.println(e);
            } catch (InstantiationException e) {
                System.out.println(e);
            } catch (IllegalAccessException e) {
                System.out.println(e);
            } catch (IllegalArgumentException e) {
                System.out.println(e);
            } catch (InvocationTargetException e) {
                System.out.println(e);
            }
        }
    }

    //###################################
    //###################################
    //## Hier geht es mit den modulen los!
    //###################################
    //###################################
    /**
     * SDisplay wie Simple Display. zeigt einfach nur das was gerade raus geht
     */
    public class NFDisplay extends Rack {

        float[] todraw;
        int c = 0;
        int maxi;
        Timer timer;
        NFDTask task;
        Option input;

        /**
         *
         * @param x
         * @param y
         */
        public NFDisplay(int x, int y) {
            super(x, y, "Display");
            input = new Option("Input", -1, 1, 0, 20, false);
            this.addOption(input);
            // TODO: speed?!
            // this.addOption(new Option("Speed", 0, 1, 0, 20, false));
            todraw = new float[512];
            h += 145;
            maxi = 128;

            timer = new Timer();
            task = new NFDTask(timer, this);
            timer.schedule(task, 5, 30);
        }

        /**
         *
         */
        @Override
        public void draw() {
            super.draw();

            fill(0, 100);
            rect(x + 5, y + 55, 140, 140);
            stroke(255, 100);
            strokeWeight(4);

            line(x + 5 + (map(c, 0, maxi, 0, 140)), y + 55, x + 5 + (map(c, 0, maxi, 0, 140)), y + 140 + 55);

            stroke(255);
            strokeWeight(1);
            for (int i = 0; i < maxi; i++) {
                line(x + 5 + (map(i, 0, maxi, 0, 140)), y + 55 - map(todraw[max(i - 1, 0)], -1, 1, -140, 0), x + 5 + (map(i + 1, 0, maxi, 0, 140)), y + 55 - map(todraw[i], -1, 1, -140, 0));
            }

        }

        @Override
        public void kill() {
            timer.cancel();
        }
    }

    public class NFDTask extends TimerTask {

        int i;
        Timer myTimer;
        NFDisplay myRack;

        /**
         *
         * @param myTimer
         * @param myRack
         */
        public NFDTask(Timer myTimer, NFDisplay myRack) {
            this.myTimer = myTimer;
            this.myRack = myRack;
            i = 0;
        }

        public void run() {
            i = (i + 1) % myRack.maxi;
            myRack.c = i;
            myRack.todraw[i] = myRack.input.value;
            //            myRack.out.setValue(myRack.offset.value + sin(i * myRack.freq.value) * myRack.amp.value);

        }
    }

    /////////////////////////////////////////////////
    /**
     *
     */
    public class LowPassRack extends EffectRack {

        /**
         *
         * @param x
         * @param y
         */
        public LowPassRack(int x, int y) {
            super(x, y, "Lowpass");
            ae = new LowPassFS(44000.0f, 44100.0f);
            this.addOption(new LoOption("Freq", 0, 1, 1));
        }
    }

    /**
     *
     */
    public class LoOption extends Option {

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         */
        public LoOption(String name, float min, float max, float value) {
            super(name, min, max, value, 20, false);
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            ((LowPassFS) ((LowPassRack) parent).ae).setFreq(this.value * 22050.0f);
        }
    }

    /**
     * SDisplay wie Simple Display. zeigt einfach nur das was gerade raus geht
     */
    public class SDisplay extends EffectRack {

        float[] todraw;
        float[] totodraw;
        int c = 500;
        int maxi;

        /**
         *
         * @param x
         * @param y
         */
        public SDisplay(int x, int y) {
            super(x, y, "Display");
            todraw = new float[512];
            totodraw = new float[512];
            h = 225;
            ae = new DisplayEffect(this);
            maxi = 128;
        }

        /**
         *
         */
        @Override
        public void draw() {
            super.draw();

            if (c++ > 5) {
                c = 0;
                arraycopy(todraw, totodraw);
            }

            fill(0, 100);
            rect(x + 5, y + 80, 140, 140);
            stroke(255);
            strokeWeight(1);

            for (int i = 0; i < maxi; i++) {
                line(x + 5 + (map(i, 0, maxi, 0, 140)), y + 80 - map(totodraw[max(i - 1, 0)], -1, 1, 0, -140), x + 5 + (map(i + 1, 0, maxi, 0, 140)), y + 80 - map(totodraw[i], -1, 1, 0, -140));
            }

        }
    }

    /**
     * Ein effekt auf Minim-basis, der aber immer die Array des Parents
     * aktuell hält, und dort die daten reinschiebt,..
     */
    public class DisplayEffect implements AudioEffect {

        SDisplay parent;

        /**
         *
         * @param parent
         */
        public DisplayEffect(SDisplay parent) {
            this.parent = parent;
        }

        /**
         *
         * @param signal
         */
        public void process(float[] signal) {
            arraycopy(signal, parent.todraw);
        }

        /**
         *
         * @param sigLeft
         * @param sigRight
         */
        public void process(float[] sigLeft, float[] sigRight) {
            // NOP
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class FFTDisplay extends EffectRack {

        float[] todraw;
        FFT fft;

        /**
         *
         * @param x
         * @param y
         */
        public FFTDisplay(int x, int y) {
            super(x, y, "FFT Display");
            h = 225;
            ae = new DisplayFFTEffect(this);
            todraw = new float[512];

            fft = new FFT(512, 44100.0f);

        }

        /**
         *
         */
        @Override
        public void draw() {
            super.draw();
            fill(0, 100);
            rect(x + 5, y + 80, 140, 140);
            stroke(255);
            strokeWeight(1);

            fft.forward(todraw);


            for (int i = 0; i < fft.specSize(); i++) {
                // draw the line for frequency band i, scaling it by 4 so we can see it a bit better
                //line(i, height, i, height - fft.getBand(i)*4);
                line(x + 5 + (map(i, 0, fft.specSize(), 0, 140)), y + 80 + 140, x + 5 + (map(i, 0, fft.specSize(), 0, 140)), y + 80 + 140 + max(-140, map(fft.getBand(i), 0, 100, 0, -140)));
            }

//    for(int i = 0; i < 512; i++)
//    {
//      line(x+5+(map(i,0,512,0,140)),y+80 - map(todraw[max(i-1,0)],-1,1,0,-140), x+5+(map(i+1,0,512,0,140)), y+80 - map(todraw[i],-1,1,0,-140));
//    }
        }
    }

    /**
     *
     */
    public class DisplayFFTEffect implements AudioEffect {

        FFTDisplay parent;

        /**
         *
         * @param parent
         */
        public DisplayFFTEffect(FFTDisplay parent) {
            this.parent = parent;
        }

        /**
         *
         * @param signal
         */
        public void process(float[] signal) {
            arraycopy(signal, parent.todraw);
        }

        /**
         *
         * @param sigLeft
         * @param sigRight
         */
        public void process(float[] sigLeft, float[] sigRight) {
            // NOP
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class InputRack extends Rack {

        float value, min, max;
        InputRect inputRect;

        /**
         *
         * @param x
         * @param y
         */
        public InputRack(int x, int y) {
            super(x, y, "Input");
            h = 170;
            min = -1;
            max = 1;
            inputRect = new InputRect(this);
            this.addOption(new Option("output", min, max, 0, 20, true));
        }

        /**
         *
         * @param p
         */
        public void addValue(float p) {
            if (value + p > max) {
                value = max;
            } else if (value + p < min) {
                value = min;
            } else {
                value += p;
            }
            ((Option) options.get(0)).setValue(value);
        }

        /**
         *
         */
        @Override
        public void draw() {
            super.draw();
            strokeWeight(5);
            noStroke();
            fill(0, 40);
            float ex = x + (w - 20) / 2 + 10;
            float ey = y + (w - 20) / 2 + 30;
            ellipse(ex, ey, w - 20, w - 20);

            stroke(0);

            for (float i = PI * 0.2f; i <= PI * 1.9f; i += PI * 0.2f) {
                line(ex + (w - 20) * 0.5f * sin(i), ey + (w - 20) * 0.5f * cos(i), ex + (w - 40) * 0.5f * sin(i), ey + (w - 40) * 0.5f * cos(i));
            }

            noStroke();
            fill(0xff9b6700);
            ellipse(ex, ey, w - 60, w - 60);
            fill(0xffffaa00);
            ellipse(ex, ey, 3, 3);

            stroke(0xffffaa00);
            strokeWeight(9);

            float tv = map(value, min, max, PI * 1.8f, PI * 0.2f);

            line(ex + (w - 80) * 0.5f * sin(tv), ey + (w - 80) * 0.5f * cos(tv), ex + (w - 50) * 0.5f * sin(tv), ey + (w - 50) * 0.5f * cos(tv));
        }
    }

    /**
     *  kleiner hack, damit auch mauseingaben abgefangen werden können,..
     */
    public class InputRect extends dragable implements drawable, closeable {

        InputRack parent;

        /**
         *
         * @param parent
         */
        public InputRect(InputRack parent) {
            this.parent = parent;
        }

        /**
         *
         * @param p
         */
        public void addValue(float p) {
            parent.addValue(map(p, -1000, 1000, -1, 1));
        }

        /**
         *
         */
        public void draw() {
        }

        /**
         *
         * @return
         */
        @Override
        public Rectangle getDrag() {
            return new Rectangle(parent.x + 10, parent.y + 30, parent.w - 20, parent.h - 40);
        }

        /**
         *
         * @return
         */
        public Rectangle getClose() {
            return new Rectangle(0, 0, 0, 0);
        }
    }

    /////////////////////////////////////////////////////
    /**
     * Bedienfeld für die Midi-Eingabe
     * TODO: konfigurierbare nummern, per txt-file midi support an/aus
     */
    public class MidiRack extends Rack {

        Option out16, out17, out18, out19, outNote, outPressed;

        /**
         *
         * @param x
         * @param y
         */
        public MidiRack(int x, int y) {
            super(x, y, "Midi");

            out16 = new COption("Control 16", -1, 1, 0, 20, true, 16);
            out17 = new COption("Control 17", -1, 1, 0, 20, true, 17);
            out18 = new COption("Control 18", -1, 1, 0, 20, true, 18);
            out19 = new COption("Control 19", -1, 1, 0, 20, true, 19);


            outPressed = new Option("Pressed", 0, 1, 0, 20, true); // wird von der note aus gesteuert!
            outNote = new NOption("Note", 0, 1, 0, 20, true, outPressed);

            this.addOption(out16);
            this.addOption(out17);
            this.addOption(out18);
            this.addOption(out19);
            this.addOption(outNote);
            this.addOption(outPressed);

            midiController.add(out16);
            midiController.add(out17);
            midiController.add(out18);
            midiController.add(out19);

            midiNotes.add(outNote);

        }

        /**
         *
         */
        @Override
        public void kill() {
            println("midi weg!");
            midiNotes.remove(outNote);
            midiController.remove(out16);
            midiController.remove(out17);
            midiController.remove(out18);
            midiController.remove(out19);
        }
    }

    /**
     * GRundlage für die midi-sachen?!
     */
    public class COption extends Option {

        int c;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param c
         */
        public COption(String name, float min, float max, float value, int h, boolean inout, int c) {
            super(name, min, max, value, h, inout);
            this.c = c;
        }

        /**
         *
         * @param num
         * @param val
         */
        public void setValue(int num, int val) {
            if (num == c) {
                this.setValue((val - 63.5f) / 63.5f);
            }

        }
    }

    /**
     *
     */
    public class NOption extends Option {

        Option pressed;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param pressed
         */
        public NOption(String name, float min, float max, float value, int h, boolean inout, Option pressed) {
            super(name, min, max, value, h, inout);
            this.pressed = pressed;
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class Inverter2 extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Inverter2(int x, int y) {
            super(x, y, "Inverter [0,1]");
            Option output = new Option("Output", 0, 1, 0, 20, true);
            Option input = new In2Option("Input", 0, 1, 0, 20, false, output);
            this.addOption(input);
            this.addOption(output);
        }
    }

    /**
     *
     */
    public class In2Option extends Option {

        Option output;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param output
         */
        public In2Option(String name, float min, float max, float value, int h, boolean inout, Option output) {
            super(name, min, max, value, h, inout);
            this.output = output;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output.setValue(1.0f - nV);
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class Inverter extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Inverter(int x, int y) {
            super(x, y, "Inverter [-1,1]");
            Option output = new Option("Output", -1, 1, 0, 20, true);
            Option input = new InOption("Input", -1, 1, 0, 20, false, output);
            this.addOption(input);
            this.addOption(output);
        }
    }

    /**
     *
     */
    public class InOption extends Option {

        Option output;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param output
         */
        public InOption(String name, float min, float max, float value, int h, boolean inout, Option output) {
            super(name, min, max, value, h, inout);
            this.output = output;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output.setValue(-nV);
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class Map1 extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Map1(int x, int y) {
            super(x, y, "Map [-1,1]>[0,1]");
            Option output = new Option("Output", 0, 1, 0, 20, true);
            Option input = new MapOption("Input", -1, 1, 0, 20, false, output);
            this.addOption(input);
            this.addOption(output);
        }
    }

    /**
     *
     */
    public class MapOption extends Option {

        Option output;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param output
         */
        public MapOption(String name, float min, float max, float value, int h, boolean inout, Option output) {
            super(name, min, max, value, h, inout);
            this.output = output;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output.setValue(map(nV, -1, 1, 0, 1));
        }
    }

    /////////////////////////////////////////////////////
    class Exp extends Rack {

        public Exp(int x, int y) {
            super(x, y, "Exp");
            Option output = new Option("Output", 0, 1, 0, 20, true);
            Option input = new ExpOption("Input", 0, 1, 0, 20, false, output);
            this.addOption(input);
            this.addOption(output);
        }
    }

    class ExpOption extends Option {

        Option output;

        public ExpOption(String name, float min, float max, float value, int h, boolean inout, Option output) {
            super(name, min, max, value, h, inout);
            this.output = output;
        }

        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output.setValue(exp((nV - 1) * 4));
        }
    }

    /////////////////////////////////////////////////////
    /**
     * Sehr einfaches Modul. Ein Eingang, zwei ausgänge, die dem eingang folgen
     */
    public class Splitter extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Splitter(int x, int y) {
            super(x, y, "Splitter");
            Option output1 = new Option("Output", -1, 1, 0, 20, true);
            Option output2 = new Option("Output", -1, 1, 0, 20, true);
            Option input = new SpOption("Input", -1, 1, 0, 20, false, output1, output2);
            this.addOption(input);
            this.addOption(output1);
            this.addOption(output2);
        }
    }

    /**
     *
     */
    public class SpOption extends Option {

        Option output1;
        Option output2;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param output1
         * @param output2
         */
        public SpOption(String name, float min, float max, float value, int h, boolean inout, Option output1, Option output2) {
            super(name, min, max, value, h, inout);
            this.output1 = output1;
            this.output2 = output2;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output1.setValue(nV);
            output2.setValue(nV);
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class Ramp extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Ramp(int x, int y) {
            super(x, y, "Ramp");
            Option output = new Option("Output", 0, 1, 0, 20, true);
            Option range = new Option("Range", 0, 1, 0, 20, false);
            Option bz = new Option("Output > 0", 0, 1, 0, 20, true);
            Option input = new RaOption("Input", 0, 1, 0, 20, false, range, output, bz);

            this.addOption(input);
            this.addOption(range);
            this.addOption(output);
            this.addOption(bz);
        }
    }

    /**
     *
     */
    public class RaOption extends Option {

        Option range;
        Option output;
        Option bz;
        Timer timer;
        Task test;

        /**
         *
         * @param name
         * @param min
         * @param max
         * @param value
         * @param h
         * @param inout
         * @param range
         * @param output
         * @param bz
         */
        public RaOption(String name, float min, float max, float value, int h, boolean inout, Option range, Option output, Option bz) {
            super(name, min, max, value, h, inout);
            this.range = range;
            this.output = output;
            this.bz = bz;
            timer = new Timer();
            test = new Task(timer, this);
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            if (nV != 0) {
                output.setValue(nV);
                timer.cancel();
                bz.setValue(1);
            } else {
                // hier das ding starten!

                output.setValue(1);
                bz.setValue(1);

                timer.cancel();
                timer = new Timer();
                test = new Task(timer, this);
                timer.schedule(test, 10, 10);

                // h\u00e4?

            }

        }
    }

    /**
     *
     */
    public class Task extends TimerTask {

        int i;
        Timer myTimer;
        RaOption myOption;

        /**
         *
         * @param myTimer
         * @param myOption
         */
        public Task(Timer myTimer, RaOption myOption) {
            this.myTimer = myTimer;
            this.myOption = myOption;
            i = 0;
        }

        public void run() {
            myOption.output.setValue(myOption.output.value - (exp(-6.0f * myOption.range.value)));
            myOption.bz.setValue(1);

            if (myOption.output.value <= 0 || myOption.value > 0) {
                myTimer.cancel();
            }

            if (myOption.output.value <= 0) {
                myOption.bz.setValue(0);
            }

        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class Adder extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Adder(int x, int y) {
            super(x, y, "Adder");
            Option output = new Option("Output", -1, 1, 0, 20, true);
            Option input2;
            Option input1 = new AdOption("Input", -1, 1, 0, 20, false, null, output);
            input2 = new AdOption("Input", -1, 1, 0, 20, false, input1, output);
            ((AdOption) input1).input = input2;
            this.addOption(input1);
            this.addOption(input2);
            this.addOption(output);
        }
    }

    /**
     *
     */
    public class AdOption extends Option {

        Option input;
        Option output;

        public AdOption(String name, float min, float max, float value, int h, boolean inout, Option input, Option output) {
            super(name, min, max, value, h, inout);
            this.input = input;
            this.output = output;
        }

        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output.setValue(nV + input.value);
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class Multiplier extends Rack {

        /**
         *
         * @param x
         * @param y
         */
        public Multiplier(int x, int y) {
            super(x, y, "Multiplier");
            Option output = new Option("Output", -1, 1, 0, 20, true);
            Option input2;
            Option input1 = new MuOption("Input", -1, 1, 0, 20, false, null, output);
            input2 = new MuOption("Input", -1, 1, 0, 20, false, input1, output);
            ((MuOption) input1).input = input2;
            this.addOption(input1);
            this.addOption(input2);
            this.addOption(output);
        }
    }

    /**
     *
     */
    public class MuOption extends Option {

        Option input;
        Option output;

        public MuOption(String name, float min, float max, float value, int h, boolean inout, Option input, Option output) {
            super(name, min, max, value, h, inout);
            this.input = input;
            this.output = output;
        }

        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            output.setValue(nV * input.value);
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class SinusModul extends Rack {

        Option freq, amp, offset, out;
        Timer timer;
        SinusTask test;

        /**
         *
         * @param x
         * @param y
         */
        public SinusModul(int x, int y) {
            super(x, y, "Sinus");
            freq = new Option("Frequenzy", 0, 1, 0, 20, false);
            amp = new Option("Amplitude", -1, 1, 0, 20, false);
            offset = new Option("Offset", -1, 1, 0, 20, false);
            out = new Option("Output", -1, 1, 0, 20, true);
            this.addOption(freq);
            this.addOption(amp);
            this.addOption(offset);
            this.addOption(out);


            timer = new Timer();
            test = new SinusTask(timer, this);
            timer.schedule(test, 1, 1);

        }

        @Override
        public void kill() {
            timer.cancel();
        }
    }

    /**
     *
     */
    public class SinusTask extends TimerTask {

        float i;
        Timer myTimer;
        SinusModul myRack;

        /**
         *
         * @param myTimer
         * @param myRack
         */
        public SinusTask(Timer myTimer, SinusModul myRack) {
            this.myTimer = myTimer;
            this.myRack = myRack;
            i = 0;
        }

        public void run() {
            i = (i + 0.4f);

            if (i * myRack.freq.value >= TWO_PI) {
                i = i - (TWO_PI / myRack.freq.value);

            }

            myRack.out.setValue(myRack.offset.value + (sin(i * myRack.freq.value) * myRack.amp.value));
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class NoiseRack extends SignalRack {

        /**
         *
         * @param x
         * @param y
         */
        public NoiseRack(int x, int y) {
            super(x, y, "White Noise");
            signal = new WhiteNoise(0.5f);
            this.addOption(new SoundOption());
            this.addOption(new AmpOptionNoise(signal));
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class SinusRack extends SignalRack {

        /**
         *
         * @param x
         * @param y
         */
        public SinusRack(int x, int y) {
            super(x, y, "Sinus");
            signal = new SineWave(440, 0.5f, 44100.0f);
            this.addOption(new SoundOption());
            this.addOption(new FreqOption(signal));
            this.addOption(new AmpOption(signal));
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class SawRack extends SignalRack {

        /**
         *
         * @param x
         * @param y
         */
        public SawRack(int x, int y) {
            super(x, y, "Saw");

            signal = new SquareWave(440, 0.5f, 44100.0f);

            this.addOption(new SoundOption());

            this.addOption(new FreqOption(signal));
            this.addOption(new AmpOption(signal));
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class FreqOption extends Option {

        AudioSignal sinus;

        FreqOption(AudioSignal sinus) {
            super("Frequency", 0, 1, 0.5f, 20, false);
            this.sinus = sinus;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            ((Oscillator) sinus).setFreq(nV * 10000);
        }
    }

    /**
     *
     */
    public class AmpOption extends Option {

        AudioSignal sinus;

        AmpOption(AudioSignal sinus) {
            super("Amplitude", 0, 1, 0.5f, 20, false);
            this.sinus = sinus;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            ((Oscillator) sinus).setAmp(nV);
        }
    }

    /**
     *
     */
    public class AmpOptionNoise extends Option {

        AudioSignal sinus;

        AmpOptionNoise(AudioSignal sinus) {
            super("Amplitude", 0, 1, 0.5f, 20, false);
            this.sinus = sinus;
        }

        /**
         *
         * @param nV
         */
        @Override
        public void setValue(float nV) {
            super.setValue(nV);
            ((WhiteNoise) sinus).setAmp(nV);
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class OutRack extends SoundRack {

        AudioOutput out;

        /**
         *
         * @param x
         * @param y
         */
        public OutRack(int x, int y) {
            super(x, y, "Output");
            this.addOption(new Option("Output", 0, 0, 0, 20, false, true));
            out = minim.getLineOut(Minim.MONO, 512);
        }

        /**
         *
         * @return
         */
        @Override
        public SoundRack getLastRack() {
            return this;
        }

        /**
         *
         * @param start
         * @param next
         */
        @Override
        public void addRack(SoundRack start, SoundRack next) {
            this.next = next;
            if (start != null) {
                this.setStart(start);
            }
        }

        /**
         *
         */
        @Override
        public void kill() {
            this.out.clearSignals();
            this.out.clearEffects();
        }

        /**
         *
         * @param start
         */
        @Override
        public void setStart(SoundRack start) {
            super.setStart(start);

            SoundRack tloop = start;
            if (start.next != null) {
                // nur wenn start.next != null !!!!

                //println("output:    " + tloop.getLastRack().name);

                //println("generator: " + tloop.name);

                this.out.addSignal(((SignalRack) tloop).signal);

                tloop = tloop.next;

                while (tloop != null) {
                    if (tloop != this) {
                        //println("effekt:    " + tloop.name);

                        this.out.addEffect(((EffectRack) tloop).ae);

                    } else {
                        //println("output:    " + tloop.name);
                    }
                    tloop = tloop.next;
                }

            }
        }
    }

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class SquareInput extends Rack {

        float vx, vy;
        SquareInputRect squareInputRect;
        /**
         *
         */
        /**
         *
         */
        /**
         *
         */
        public Option o1, o2, o3;

        /**
         *
         * @param x
         * @param y
         */
        public SquareInput(int x, int y) {
            super(x, y, "Square Input");
            h = 170;
            squareInputRect = new SquareInputRect(this);

            //hier weiter machen! values setzten f\u00fcr die optionen!
            o1 = new Option("output x", -1, 1, 0, 20, true);
            o2 = new Option("output y", -1, 1, 0, 20, true);
            o3 = new Option("pressed", 0, 1, 0, 20, true);

            this.addOption(o1);
            this.addOption(o2);
            this.addOption(o3);
        }

        /**
         *
         * @param vx
         * @param vy
         */
        public void setLoc(float vx, float vy) {
            this.vx = vx;
            this.vy = vy;
            o1.setValue(map(vx, -65, 65, -1, 1));
            o2.setValue(map(vy, -65, 65, -1, 1));
            o3.setValue(1);
        }

        /**
         *
         */
        @Override
        public void draw() {
            super.draw();
            noStroke();
            fill(0, 40);
            float ex = x + (w - 20) / 2 + 10;
            float ey = y + (w - 20) / 2 + 30;
            rect(x + 10, y + 30, w - 20, w - 20);
            fill(0xffffaa00);
            ellipse((float) squareInputRect.getDrag().getCenterX() + map(o1.value, -1, 1, -65, 65), (float) squareInputRect.getDrag().getCenterY() + map(o2.value, -1, 1, -65, 65), 3, 3);
        }
    }

    /**
     *
     */
    public class SquareInputRect extends dragable implements drawable, closeable {

        SquareInput parent;

        SquareInputRect(SquareInput parent) {
            this.parent = parent;
        }

        /**
         *
         * @param vx
         * @param vy
         */
        public void setLoc(float vx, float vy) {
            parent.setLoc(vx - (float) this.getDrag().getCenterX(), vy - (float) this.getDrag().getCenterY());
        }

        /**
         *
         */
        public void draw() {
        }

        /**
         *
         * @return
         */
        @Override
        public Rectangle getDrag() {
            return new Rectangle(parent.x + 10, parent.y + 30, parent.w - 20, parent.w - 20);
        }

        /**
         *
         * @return
         */
        public Rectangle getClose() {
            return new Rectangle(0, 0, 0, 0);
        }
    }
    /////////////////////////////////////////////////////

    /////////////////////////////////////////////////////
    /**
     *
     */
    public class TriggerOption extends Option {

        boolean plusminus;
        float oV;

        TriggerOption(String name, float min, float max, float start, int h, boolean inout, boolean plusminus, SandHModul parent) {
            super(name, min, max, start, h, inout);
            this.plusminus = plusminus;
            this.oV = start;
        }

        @Override
        public void setValue(float nV) {
            {
                super.setValue(nV);

                float trig = ((SandHModul) (this.parent)).triggerV.value;

                if (plusminus) {
                    if ((nV >= trig) && (oV < trig)) {
                        ((SandHModul) (this.parent)).start();
                    }
                } else {
                    if ((nV <= trig) && (oV > trig)) {
                        ((SandHModul) (this.parent)).start();
                    }
                }
            }
            oV = nV;
        }
    }

    public class SandHModul extends Rack {

        Option triggerp, triggerm, triggerout, sample, hold, notHold, triggerV, out;
        Timer timer;
        SandHTask test;

        /**
         *
         * @param x
         * @param y
         */
        public SandHModul(int x, int y) {
            super(x, y, "Sample and Hold");
            triggerp = new TriggerOption("Trigger (+)", -1, 1, 0, 20, false, true, this);
            triggerm = new TriggerOption("Trigger (-)", -1, 1, 0, 20, false, false, this);
            triggerV = new Option("Trigger Value", -1, 1, (float) 0.5, 20, false);
            triggerout = new Option("Trigger Out", -1, 1, 0, 20, true);
            sample = new Option("Sample", -1, 1, 1, 20, false);
            notHold = new Option("Sample !Hold", -1, 1, 0, 20, false);
            hold = new Option("Duration", 0, 1, (float) 0.02, 20, false);
            out = new Option("Output", -1, 1, 0, 20, true);
            this.addOption(triggerp);
            this.addOption(triggerm);
            this.addOption(triggerV);
            this.addOption(triggerout);
            this.addOption(sample);
            this.addOption(notHold);
            this.addOption(hold);
            this.addOption(out);
        }

        @Override
        public void kill() {
            if (timer != null) {
                timer.cancel();
            }
        }

        public void start() {
            timer = new Timer();
            test = new SandHTask(timer, this);
            timer.schedule(test, (long) (10000 * this.hold.value), 1);
            out.setValue(sample.value);
            triggerout.setValue(1);
        }

        public void stop() {
            timer.cancel();
            out.setValue(notHold.value);
            triggerout.setValue(-1);
        }
    }

    /**
     *
     */
    public class SandHTask extends TimerTask {

        Timer myTimer;
        SandHModul myRack;

        public SandHTask(Timer myTimer, SandHModul myOption) {
            this.myTimer = myTimer;
            this.myRack = myOption;
        }

        public void run() {

            ((SandHModul) (myRack)).stop();

            //println("hier!");

            myTimer.cancel();
        }
    }

////////////////////////////////////////////////////////////////
    public class Glider extends Rack {

        GlOption input1;

        public Glider(int x, int y) {
            super(x, y, "Glider");
            Option output = new Option("Output", -1, 1, 0, 20, true);
            Option faktorUp = new Option("Speed up", -1, 1, (float) 0.5, 20, false);
            Option faktorDown = new Option("Speed down", -1, 1, (float) 0.1, 20, false);
            input1 = new GlOption("Input", -1, 1, 0, 20, false, output, faktorUp, faktorDown);
            this.addOption(input1);
            this.addOption(faktorUp);
            this.addOption(faktorDown);
            this.addOption(output);
        }

        @Override
        public void kill() {
            // kill da timer
            input1.timer.cancel();

        }
    }

    public class GlOption extends Option {

        Option output;
        Option faktorUp, faktorDown;
        Timer timer;
        GlTask test;

        public GlOption(String name, float min, float max, float value, int h, boolean inout, Option output, Option faktorUp, Option faktorDown) {
            super(name, min, max, value, h, inout);
            this.output = output;
            this.faktorUp = faktorUp;

            // make da timer
            timer = new Timer();
            test = new GlTask(timer, output, this, faktorUp, faktorDown);
            timer.schedule(test, 1, 1);


        }

        @Override
        public void setValue(float nV) {
            super.setValue(nV);

           // output.value = ();
            // set value wird vom task aufgerufen!
            // TODO: autsch,.. funktioniert nicht :-/
        }
    }

    public class GlTask extends TimerTask {

        Timer myTimer;
        Option output, input, faktorUp, faktorDown;

        public GlTask(Timer myTimer, Option output, Option input, Option faktorUp, Option faktorDown) {
            this.myTimer = myTimer;
            this.output = output;
            this.input = input;
            this.faktorUp = faktorUp;
            this.faktorDown = faktorDown;
        }

        public void run() {
            if(input.value > output.value)
                {
            output.setValue((float) ((input.value * (faktorUp.value*0.05) ) + (output.value * (1 - (faktorUp.value*0.05)))));
            }
            else
                {
            output.setValue((float) ((input.value * (faktorDown.value*0.05) ) + (output.value * (1 - (faktorDown.value*0.05)))));
                }
            }
           // println("here");
            // just recall this option so that it can be recalculatet
        }
    
    //
    //
    //
    }

