/*
  iPhone
 */


class IPHONE extends Shader {
  boolean webcamRunning;
  PImage img;
  int rw, rh, x, y, w, h, croptop, cropbottom, cropleft, cropright, contrast, brightness, hue, saturation, camSelected, camSelectedPrev;
  final int[] blends = {BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN};
  IPHONE() {
    rw = canvas.width;
    rh = canvas.height;
    name = "Webcam";
    println(Capture.list().length);
    img = createImage(rw, rh, ARGB);
    if (!webcamRunning) startWebcam();
    //params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("x", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    params.add(new Param("y", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("w", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("h", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("croptop", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("cropbottom", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("cropleft", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("cropright", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("contrast", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("brightness", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("hue", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("saturation", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("blend mode", INTVAL, 0, this.blends.length-1, new int[]{RANDOM }));
    params.add(new Param("webcam", INTVAL, 0, Capture.list().length-1, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    camSelected = (int)params.get(13).getValue();
    camSelectedPrev = (int)params.get(13).getValue();
  }

  void startWebcam() {
    String[] cameras = Capture.list();
    println("Hi");
    for (int i = 0; i < cameras.length; i++) {
      println(cameras[i]);
    }
    //println(cameras);
    cam2.start();
    webcamRunning = true;
  }
  
  void stopWebcam() { //gets never called lmao
    cam2.stop();
    webcamRunning = false;
  }
  
  
  void apply() {
    rw = canvas.width;
    rh = canvas.height;
    x = int(params.get(0).getValue()*rw);
    y = int(params.get(1).getValue()*rh);
    w = int(params.get(2).getValue()*(rw*2));
    h = int(params.get(3).getValue()*(rh*2));


    camSelected = (int)params.get(13).getValue();
    /*
    if (camSelected != camSelectedPrev) {
     currentCam = camSelected;
     camSelectedPrev = camSelected;
     stopWebcam();
     startWebcam();
     //new Capture(this, 1920,1080, Capture.list()[camSelected]);
     }*/



    //params.get(13) //camera id
    //if (cam.available()) img = cam.get();
    img = iPhoneImg.get();

    canvas.beginDraw();
    canvas.image(img, x, y, w, h);
    //for ( int i = 0, l = canvas.pixels.length; i<l; i++) {
    //}
    canvas.endDraw();
  }
  void getParams() {
  }
}


/*

 Webcam
 
 */

/*
boolean webcamRunning;
 void startWebcam() {
 String[] cameras = Capture.list();
 println("Hi");
 for (int i = 0; i < cameras.length; i++) {
 println(cameras[i]);
 }
 //println(cameras);
 cam.start();
 webcamRunning = true;
 }
 void stopWebcam() {
 cam.stop();
 webcamRunning = false;
 }
 */

class WEBCAM extends Shader {
  boolean webcamRunning;
  PImage img;
  int rw, rh, x, y, w, h, croptop, cropbottom, cropleft, cropright, contrast, brightness, hue, saturation, camSelected, camSelectedPrev;
  final int[] blends = {BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN};
  WEBCAM() {
    rw = canvas.width;
    rh = canvas.height;
    name = "Webcam";
    println(Capture.list().length);
    img = createImage(rw, rh, ARGB);
    if (!webcamRunning) startWebcam();
    //params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("x", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    params.add(new Param("y", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("w", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("h", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("croptop", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("cropbottom", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("cropleft", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("cropright", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("contrast", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("brightness", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("hue", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("saturation", FLOATVAL, 0.0, 1.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("blend mode", INTVAL, 0, this.blends.length-1, new int[]{RANDOM }));
    params.add(new Param("webcam", INTVAL, 0, Capture.list().length-1, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    camSelected = (int)params.get(13).getValue();
    camSelectedPrev = (int)params.get(13).getValue();
  }

  void startWebcam() {
    String[] cameras = Capture.list();
    println("Hi");
    for (int i = 0; i < cameras.length; i++) {
      println(cameras[i]);
    }
    //println(cameras);
    cam.start();
    webcamRunning = true;
  }
  
  void stopWebcam() { //gets never called lmao
    cam.stop();
    webcamRunning = false;
  }
  
  
  void apply() {
    rw = canvas.width;
    rh = canvas.height;
    x = int(params.get(0).getValue()*rw);
    y = int(params.get(1).getValue()*rh);
    w = int(params.get(2).getValue()*(rw*2));
    h = int(params.get(3).getValue()*(rh*2));


    camSelected = (int)params.get(13).getValue();
    /*
    if (camSelected != camSelectedPrev) {
     currentCam = camSelected;
     camSelectedPrev = camSelected;
     stopWebcam();
     startWebcam();
     //new Capture(this, 1920,1080, Capture.list()[camSelected]);
     }*/



    //params.get(13) //camera id
    //if (cam.available()) img = cam.get();
    img = webcamImg.get();

    canvas.beginDraw();
    canvas.image(img, x, y, w, h);
    //for ( int i = 0, l = canvas.pixels.length; i<l; i++) {
    //}
    canvas.endDraw();
  }
  void getParams() {
  }
}

/*

 ASDFPIXELSORT
 by Kim Asendorf
 
 */

class ASDFPIXELSORT extends Shader {
  ASDFPIXELSORT() {
    name = "fxASDFPixelSort";
    params.add(new Param("black", INTVAL, -17000000, -2000000, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("target", INTVAL, 0, 2, new int[]{RANDOM}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 2;
  }
  int previousMode;
  void apply() {
    if (previousMode != int(params.get(1).getValue())) {
      if (params.get(1).getValue() == 0) changeParam(0, new Param("black", INTVAL, -17000000, -2000000, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
      if (params.get(1).getValue() == 1) changeParam(0, new Param("brightness", INTVAL, 0, 200, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
      if (params.get(1).getValue() == 2) changeParam(0, new Param("white", INTVAL, -15000000, -700000, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    }
    previousMode = (int)params.get(1).getValue();

    row = 0;
    column = 0;
    colorMode(RGB);
    canvas.beginDraw();
    while (column < canvas.width-1) {
      canvas.loadPixels();
      sortColumn();
      column++;
      canvas.updatePixels();
    }

    while (row < canvas.height-1) {
      canvas.loadPixels();
      sortRow();
      row++;
      canvas.updatePixels();
    }
    canvas.endDraw();
  }

  int row = 0;
  int column = 0;
  void sortRow() {
    int x = 0;
    int y = row;
    int xend = 0;
    while (xend < canvas.width-1) {
      switch((int)params.get(1).getValue()) {
      case 0:
        x = getFirstNotBlackX(x, y);
        xend = getNextBlackX(x, y);
        break;
      case 1:
        x = getFirstBrightX(x, y);
        xend = getNextDarkX(x, y);
        break;
      case 2:
        x = getFirstNotWhiteX(x, y);
        xend = getNextWhiteX(x, y);
        break;
      default:
        break;
      }

      if (x < 0) break;

      int sortLength = xend-x;

      color[] unsorted = new color[sortLength];
      color[] sorted = new color[sortLength];

      for (int i=0; i<sortLength; i++) {
        unsorted[i] = canvas.pixels[x + i + y * canvas.width];
      }

      sorted = sort(unsorted);

      for (int i=0; i<sortLength; i++) {
        canvas.pixels[x + i + y * canvas.width] = sorted[i];
      }

      x = xend+1;
    }
  }


  void sortColumn() {
    int x = column;
    int y = 0;
    int yend = 0;

    while (yend < canvas.height-1) {
      switch((int)params.get(1).getValue()) {
      case 0:
        y = getFirstNotBlackY(x, y);
        yend = getNextBlackY(x, y);
        break;
      case 1:
        y = getFirstBrightY(x, y);
        yend = getNextDarkY(x, y);
        break;
      case 2:
        y = getFirstNotWhiteY(x, y);
        yend = getNextWhiteY(x, y);
        break;
      default:
        break;
      }

      if (y < 0) break;

      int sortLength = yend-y;
      color[] unsorted = new color[sortLength];
      color[] sorted = new color[sortLength];
      for (int i=0; i<sortLength; i++) {
        unsorted[i] = canvas.pixels[x + (y+i) * canvas.width];
      }

      sorted = sort(unsorted);


      for (int i=0; i<sortLength; i++) {
        canvas.pixels[x + (y+i) * canvas.width] = sorted[i];
      }

      y = yend+1;
    }
  }


  //BLACK
  int getFirstNotBlackX(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    while ( (c = canvas.pixels[x + y * canvas.width]) < params.get(0).getValue()) {
      x++;
      if (x >= canvas.width) return -1;
    }
    return x;
  }

  int getNextBlackX(int _x, int _y) {
    int x = _x+1;
    int y = _y;
    color c;
    while ( (c = canvas.pixels[x + y * canvas.width]) > params.get(0).getValue()) {
      x++;
      if (x >= canvas.width) return canvas.width-1;
    }
    return x-1;
  }

  //BRIGHTNESS
  int getFirstBrightX(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    while (brightness (c = canvas.pixels[x + y * canvas.width]) < params.get(0).getValue()) {
      x++;
      if (x >= canvas.width) return -1;
    }
    return x;
  }

  int getNextDarkX(int _x, int _y) {
    int x = _x+1;
    int y = _y;
    color c;
    while (brightness (c = canvas.pixels[x + y * canvas.width]) > params.get(0).getValue()) {
      x++;
      if (x >= canvas.width) return canvas.width-1;
    }
    return x-1;
  }

  //WHITE
  int getFirstNotWhiteX(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    while ( (c = canvas.pixels[x + y * canvas.width]) > params.get(0).getValue()) {
      x++;
      if (x >= canvas.width) return -1;
    }
    return x;
  }

  int getNextWhiteX(int _x, int _y) {
    int x = _x+1;
    int y = _y;
    color c;
    while ( (c = canvas.pixels[x + y * canvas.width]) < params.get(0).getValue()) {
      x++;
      if (x >= canvas.width) return canvas.width-1;
    }
    return x-1;
  }


  //BLACK
  int getFirstNotBlackY(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    if (y < canvas.height) {
      while ( (c = canvas.pixels[x + y * canvas.width]) < params.get(0).getValue()) {
        y++;
        if (y >= canvas.height) return -1;
      }
    }
    return y;
  }

  int getNextBlackY(int _x, int _y) {
    int x = _x;
    int y = _y+1;
    color c;
    if (y < canvas.height) {
      while ( (c = canvas.pixels[x + y * canvas.width]) > params.get(0).getValue()) {
        y++;
        if (y >= canvas.height) return canvas.height-1;
      }
    }
    return y-1;
  }

  //BRIGHTNESS
  int getFirstBrightY(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    if (y < canvas.height) {
      while (brightness (c = canvas.pixels[x + y * canvas.width]) < params.get(0).getValue()) {
        y++;
        if (y >= canvas.height) return -1;
      }
    }
    return y;
  }

  int getNextDarkY(int _x, int _y) {
    int x = _x;
    int y = _y+1;
    color c;
    if (y < canvas.height) {
      while (brightness (c = canvas.pixels[x + y * canvas.width]) > params.get(0).getValue()) {
        y++;
        if (y >= canvas.height) return canvas.height-1;
      }
    }
    return y-1;
  }

  //WHITE
  int getFirstNotWhiteY(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    if (y < canvas.height) {
      while ( (c = canvas.pixels[x + y * canvas.width]) > params.get(0).getValue()) {
        y++;
        if (y >= canvas.height) return -1;
      }
    }
    return y;
  }

  int getNextWhiteY(int _x, int _y) {
    int x = _x;
    int y = _y+1;
    color c;
    if (y < canvas.height) {
      while ( (c = canvas.pixels[x + y * canvas.width]) < params.get(0).getValue()) {
        y++;
        if (y >= canvas.height) return canvas.height-1;
      }
    }
    return y-1;
  }
}


/*

 DISTORTER
 by Tomasz Sulej
 
 */

class DISTORTER extends Shader {
  boolean do_blend = false; // blend image after process
  int blend_mode = OVERLAY; // blend type
  int channel = BRIGHTNESS; // channel used in processing (R,G,B) or (H,S,B)
  float scalex = 0.05; // from 0.01 to 1
  float scaley = 0.1; // from 0.01 to 1
  boolean shift_hue = true;
  float shift_amt = 0.1; // from 0 to 1

  PImage buffer;

  final static int distortionMatrixSize = 512; //doesnt really make a difference, it's more or less "noise variety".. only kinda different if real low, like 2 or so

  int[][] distort = new int[2][distortionMatrixSize];
  final static float tick = 1.0/distortionMatrixSize;

  int mode = 0;
  int initBufferW, initBufferH;

  DISTORTER() {

    buffer = createImage(canvas.width, canvas.height, ARGB);
    initBufferW = buffer.width;
    initBufferH = buffer.height;

    name = "fxDistorter";
    params.add(new Param("width", FLOATVAL, 2, buffer.width/4-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("height", FLOATVAL, 2, buffer.height/4-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("shift hue amount", FLOATVAL, 0, 1, new int[]{SAWTOOTH, SAWTOOTHINVERSE, TAN, TANINVERSE, RAMP, RAMPINVERSE}));
    params.add(new Param("scale x", FLOATVAL, 0.01, 1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("scale y", FLOATVAL, 0.01, 1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("blend mode", INTVAL, 0, blends.length-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("channel", INTVAL, 0, 12, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 8;

    //params.add(new Param(DIRECTION));

    // channel, blend_mode

    //params.add(new Param("height", FLOATVAL, 1, buffer.height/4-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));


    // prepare distortion pattern

    for (int i=0; i<distortionMatrixSize; i++) {
      distort[0][i] = (int)random(-128, 128);
      distort[1][i] = (int)random(-128, 128);
    }
  }



  // ALL Channels, Nxxx stand for negative (255-value)
  // channels to work with
  final static int RED = 0;
  final static int GREEN = 1;
  final static int BLUE = 2;
  final static int HUE = 3;
  final static int SATURATION = 4;
  final static int BRIGHTNESS = 5;
  final static int NRED = 6;
  final static int NGREEN = 7;
  final static int NBLUE = 8;
  final static int NHUE = 9;
  final static int NSATURATION = 10;
  final static int NBRIGHTNESS = 11;

  void apply() {
    buffer = canvas.get();
    buffer.resize(canvas.width, canvas.height);

    float neww = map(params.get(0).getValue(), 2, initBufferW-2, 2, buffer.width/4-2);
    float newh = map(params.get(1).getValue(), 2, initBufferH-2, 2, buffer.height/4-2);


    /*
    do_blend = boolean(int(params.get(2).getValue()));
     shift_amt = params.get(3).getValue();
     scalex = params.get(4).getValue();
     scaley = params.get(5).getValue();
     blend_mode = blends[(int)params.get(6).getValue()];
     channel = (int)params.get(7).getValue();
     */
    do_blend = boolean(int(params.get(2).getValue()));
    shift_amt = params.get(3).getValue();
    scalex = params.get(4).getValue();
    scaley = params.get(5).getValue();
    blend_mode = blends[(int)params.get(6).getValue()];
    channel = (int)params.get(7).getValue();

    float totalnum = neww+newh;
    float times = (totalnum/floor(totalnum/neww));
    float offx = (totalnum%neww)/times;
    float ratiox = neww/buffer.width;
    //println(ratiox);

    canvas.beginDraw();
    canvas.noStroke();

    for (int y=0; y<buffer.height; y++) {
      float yy = y/(float)buffer.height;
      for (int x=0; x<buffer.width; x++) {
        float xx = x/(float)buffer.width;

        float offy = floor(newh*yy);
        float fx = xx*ratiox+offx*offy;

        float shift = fx%1.0;
        float st = shift/tick;
        int no1 = floor(st)%distortionMatrixSize;
        int no2 = ceil(st)%distortionMatrixSize  ;
        float l = st-(float)no1;

        float cx = lerp(distort[0][no1], distort[0][no2], l);
        float cy = lerp(distort[1][no1], distort[1][no2], l);

        float rx =getChannel(buffer.get(x, y), channel);
        int sx = (int)((buffer.width+x+cx*rx*scalex*0.1)%buffer.width);
        int sy = (int)((buffer.height+y+cy*scaley)%buffer.height);

        color c=buffer.get(sx, sy);

        if (shift_hue) {
          colorMode(HSB, 255);
          c = color((hue(c)+shift_amt*255*noise(newh+y))%255.0, constrain(saturation(c)*1.2, 0, 255), constrain(brightness(c), 0, 255));
          colorMode(RGB, 255);
        }
        //  buffer.fill(lerpColor(c,img.get(x,y),0.2));
        canvas.fill(c); //wärs nich effizienter die pixelmatrix zu ändern ?
        canvas.rect(x, y, 1, 1);
      }
    }

    if (do_blend)
      canvas.blend(buffer, 0, 0, buffer.width, buffer.height, 0, 0, canvas.width, canvas.height, blend_mode);

    canvas.endDraw();
  }



  float getChannel(color c, int channel) {
    int ch = channel>5?channel-6:channel;
    float cc;

    switch(ch) {
    case RED:
      cc = red(c);
      break;
    case GREEN:
      cc = green(c);
      break;
    case BLUE:
      cc = blue(c);
      break;
    case HUE:
      cc = hue(c);
      break;
    case SATURATION:
      cc = saturation(c);
      break;
    default:
      cc= brightness(c);
      break;
    }

    return channel>5?255-cc:cc;
  }
}

/*

 FM
 
 */


class FM extends Shader {
  // configuration
  int colorspace = RGB;
  int quantval = 30; // 0 - off, less - more glitch, more - more precision
  boolean do_blend = true; // blend image after process
  int blend_mode = OVERLAY; // blend type



  //unused parameters (giers):
  final static boolean first_channel_only = false; // for L.. or Y.. colorspaces set true to modulate only luma;
  final static boolean lowpass1_on = true; // on/off of first low pass filter
  final static boolean lowpass2_on = true; // on/off of second low pass filter
  final static boolean lowpass3_on = true; // on/off of third low pass filter
  // better don't touch it, lowpass filters are run in cascade
  float lowpass1_cutoff = 0.25; // percentage of rate
  float lowpass2_cutoff = 0.1;
  float lowpass3_cutoff = 0.05;



  // working buffer

  PGraphics buffer;

  // local variables
  float min_omega, max_omega;
  float min_phase_mult=0.05;
  float max_phase_mult=50.0;
  LowpassFilter lpf1, lpf2, lpf3;
  int[][] pxls;
  boolean negate = false;

  FM() {
    name = "fxFM";
    params.add(new Param ("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("blend_mode", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param ("omega", FLOATVAL, 0, 1, new int[]{SINE, SAWTOOTH, TRIANG}));
    params.add(new Param ("phase", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("colorspace", INTVAL, 0, 16, new int[]{RANDOM}));
    params.add(new Param ("quant", INTVAL, 0, 40, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 6;

    buffer = createGraphics(canvas.width, canvas.height);
    buffer.beginDraw();
    buffer.noStroke();
    //buffer.smooth(8);
    //buffer.background(0);
    // buffer.image(canvas, 0, 0);
    buffer.endDraw();


    float rate = 100000.0;

    lpf1 = new LowpassFilter(rate, lowpass1_cutoff*rate);
    lpf2 = new LowpassFilter(rate, lowpass2_cutoff*rate);
    lpf3 = new LowpassFilter(rate, lowpass3_cutoff*rate);

    //img.loadPixels();
  }
  void prepareData() {
    pxls = new int[3][canvas.pixels.length];
    for (int i=0; i<canvas.pixels.length; i++) {
      int cl = toColorspace(canvas.pixels[i], colorspace);
      pxls[0][i] = (cl >> 16) & 0xff;
      pxls[1][i] = (cl >> 8) & 0xff;
      pxls[2][i] = (cl) & 0xff;
    }
  }

  float omega, min_phase, max_phase;


  int rw, rh;
  void apply() {

    buffer.setSize(canvas.width, canvas.height);

    min_omega = TWO_PI/(0.05*canvas.width);
    max_omega = TWO_PI/(300.0*canvas.width);

    rw = canvas.width;

    do_blend = boolean(int(params.get(0).getValue()));
    blend_mode = blends[(int)params.get(1).getValue()];
    omega = map(sqrt(params.get(2).getValue()), 0, 1, min_omega, max_omega);
    float phase = map(sq(params.get(3).getValue()), 0, 1, min_phase_mult, max_phase_mult);
    colorspace = (int)params.get(4).getValue();
    quantval = (int) params.get(5).getValue();


    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      min_omega = TWO_PI/(0.05*canvas.width);
      max_omega = TWO_PI/(300.0*canvas.width);
    }
    prepareData();

    //buffer = canvas.get(0, 0, canvas.width, canvas.height);


    max_phase = phase * omega;
    min_phase = -max_phase;

    processImage();
  }

  void processImage() {
    buffer.beginDraw();
    buffer.loadPixels();

    int [][] dest_pxls = new int[3][canvas.pixels.length];

    if (first_channel_only) {
      arrayCopy(pxls[1], dest_pxls[1]);
      arrayCopy(pxls[2], dest_pxls[2]);
    }

    for (int i=0; i< (first_channel_only?1:3); i++) {
      for (int y=0; y<canvas.height; y++) {
        int off = y * canvas.width;

        //reset filters each line
        lpf1.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
        lpf2.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
        lpf3.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));

        float sig_int = 0; // integral of the signal
        float pre_m = 0; // previous value of modulated signal

        for (int x=0; x<canvas.width; x++) {

          /////////////////////////
          // FM part starts here
          /////////////////////////

          float sig = map(pxls[i][x+off], 0, 255, min_phase, max_phase); // current signal value
          sig_int += sig; // current value of signal integral

          float m = cos(omega * x + sig_int); // modulate signal

          if ( quantval > 0) {
            m = map((int)map(m, -1, 1, 0, quantval), 0, quantval, -1, 1); // quantize
          }

          float dem = abs(m-pre_m); // demodulate signal, derivative
          pre_m = m; // remember current value

          // lowpass filter chain
          if (lowpass1_on) dem = lpf1.lowpass(dem);
          if (lowpass2_on) dem = lpf2.lowpass(dem);
          if (lowpass3_on) dem = lpf3.lowpass(dem);

          // remap signal back to channel value
          int v = constrain( (int)map(2*(dem-omega), min_phase, max_phase, 0, 255), 0, 255);

          //////////////////////
          // FM part ends here
          //////////////////////

          dest_pxls[i][x+off] = negate?255-v:v;
        }
      }
    }

    for (int i=0; i<buffer.pixels.length; i++) {
      buffer.pixels[i] = fromColorspace(0xff000000 | (dest_pxls[0][i] << 16) |  (dest_pxls[1][i] << 8) |  (dest_pxls[2][i]), colorspace);
    }

    buffer.updatePixels();

    if (do_blend)
      buffer.blend(canvas, 0, 0, canvas.width, canvas.height, 0, 0, buffer.width, buffer.height, blend_mode);

    buffer.endDraw();
    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2, canvas.width, canvas.height);
    canvas.endDraw();
  }


  class LowpassFilter {
    float alpha;
    float prev;

    public LowpassFilter(float rate, float hz) {
      alpha = 0.0;
      prev = 0.0;
      setFilter(rate, hz);
    }

    void setFilter(float rate, float hz) {
      float timeInterval = 1.0/rate;
      float tau = 1.0 / (hz * TWO_PI);
      alpha = timeInterval / (tau + timeInterval);
    }

    void resetFilter(float val) {
      prev = val;
    }

    void resetFilter() {
      resetFilter(0);
    }

    float lowpass(float sample) {
      float stage1 = sample * alpha;
      float stage2 = prev - (prev * alpha);
      prev = (stage1 + stage2);
      return prev;
    }

    float highpass(float sample) {
      return sample - lowpass(sample);
    }
  }
}


/*

 WZIP
 
 */


class WZIP extends Shader {


  final float sqrt05 = sqrt(0.5);

  float[] raw, raw1, raw2, raw3;
  float[] in, w, out;
  float[] in1, in2, in3, out1, out2, out3;
  int n, n2, s;
  float scalingfactorin, scalingfactorout;

  PImage img;
  String sessionid;



  WZIP() {

    name = "fxWZIP";
    params.add(new Param ("scale", FLOATVAL, 0.1, 1000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("factor in", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("factor out", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("hsb/rgb", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 5;

    sessionid = hex((int)random(0xffff), 4);
    img = createImage(canvas.width, canvas.height, ARGB);
    img = canvas.get(0, 0, canvas.width, canvas.height);
  }

  void apply() {
    //    img = createImage(canvas.width, canvas.height, ARGB);
    img.resize(canvas.width, canvas.height);
    img = canvas.get(0, 0, canvas.width, canvas.height);

    s = img.width*img.height;
    raw = new float[s*3];
    raw1 = new float[s];
    raw2 = new float[s];
    raw3 = new float[s];
    canvas.beginDraw();


    canvas.background(0);
    canvas.noStroke();


    if (boolean((int)params.get(3).getValue())) { /////////////////////
      canvas.colorMode(HSB, 255);
      colorMode(HSB, 255);
    } else {
      canvas.colorMode(RGB, 255);
      colorMode(RGB, 255);
    }

    scalingfactorin = map(params.get(1).getValue(), 0, 1, 0, params.get(0).getValue()); /////////////////////
    scalingfactorout = map(params.get(2).getValue(), 0, 1, 0, params.get(0).getValue()); /////////////////////



    int iter=0;
    int iter2 = 0;
    for (int y=0; y<img.height; y++) {
      for (int x=0; x<img.width; x++) {
        color c = img.get(x, y);
        float r, g, b;
        if (boolean((int)params.get(3).getValue())) { /////////////////////
          r = hue(c)>127?hue(c)-256:hue(c);
          g = saturation(c)>127?saturation(c)-256:saturation(c);
          b = brightness(c)>127?brightness(c)-256:brightness(c);
        } else {
          r = red(c)>127?red(c)-256:red(c);
          g = green(c)>127?green(c)-256:green(c);
          b = blue(c)>127?blue(c)-256:blue(c);
        }
        raw[iter++] = r;
        raw[iter++] = g;
        raw[iter++] = b;
        raw1[iter2] = r;
        raw2[iter2] = g;
        raw3[iter2] = b;
        iter2++;
      }
    }

    n = (int)pow(2, ceil(log(s*3)/log(2)));
    n2 = (int)pow(2, ceil(log(s)/log(2)));

    in = new float[n];
    w = new float[n];
    out = new float[n];
    out1 = new float[n2];
    out2 = new float[n2];
    out3 = new float[n2];
    in1 = new float[n2];
    in2 = new float[n2];
    in3 = new float[n2];

    arrayCopy(raw, 0, in, 0, raw.length);
    for (int i=raw.length; i<n; i++) in[i] = raw[raw.length-1];

    arrayCopy(raw1, 0, in1, 0, s);
    arrayCopy(raw2, 0, in2, 0, s);
    arrayCopy(raw3, 0, in3, 0, s);

    for (int i=s; i<n2; i++) {
      in1[i] = raw1[s-1];
      in2[i] = raw2[s-1];
      in3[i] = raw3[s-1];
    }


    if (boolean((int)params.get(4).getValue())) option1(); /////////////////////
    else option2();


    canvas.colorMode(RGB);
    colorMode(RGB);


    canvas.endDraw();
  }

  float clamp(float c) {
    return(abs(c<0?256+c:c)%255.0);
  }
  void option2() {
    wtrafo(in1, n2);
    wbtrafo(out1, n2);

    wtrafo(in2, n2);
    wbtrafo(out2, n2);

    wtrafo(in3, n2);
    wbtrafo(out3, n2);

    for (int i=0; i<s; i++) {
      float r = clamp(out1[i]);
      float g = clamp(out2[i]);
      float b = clamp(out3[i]);
      canvas.fill(r, g, b);
      canvas.rect(i%canvas.width, i/canvas.width, 1, 1);
    }
  }
  void option1() {
    wtrafo(in, n);
    wbtrafo(out, n);

    float r=0, g=0, b=0;
    int state = 0;

    for (int i=0; i<raw.length; i++) {
      float c = clamp(out[i]);
      switch(state) {
      case 0:
        r = c;
        break;
      case 1:
        g = c;
        break;
      case 2:
        b = c;
        break;
      default:
        {
          r = c;
          canvas.fill(r, g, b);
          canvas.rect(floor(i/3.0)%canvas.width, floor(i/3.0)/canvas.width, 1, 1);
          state = 0;
        }
      }
      state++;
    }
  }




  void wbtrafo(float[] y, int n) {
    float[] d = new float[n];
    d[n-2] = w[n-1];
    int b1 = n-4;
    int b2 = n-2;
    int a=1;
    while (a<n/2) {
      for (int i=0; i<a; i++) {
        d[2*i+b1]=(d[i+b2]+w[i+b2])*sqrt05;
        d[2*i+1+b1]=(d[i+b2]-w[i+b2])*sqrt05;
      }
      b2=b1;
      b1=b1-4*a;
      a*=2;
    }

    for (int i=0; i<a; i++) {
      y[2*i]=(d[i]+w[i])*sqrt05;
      y[2*i+1]=(d[i]-w[i])*sqrt05;
    }

    for (int i=0; i<n; i++) y[i] *= scalingfactorout;
  }

  void wtrafo(float[] y, int n) {
    float[] d = new float[n];
    int a = n/2;
    for (int i=0; i<a; i++) {
      w[i] = (y[2*i]-y[2*i+1])*sqrt05;
      d[i] = (y[2*i]+y[2*i+1])*sqrt05;
    }
    int b1 = 0;
    int b2 = a;
    a/=2;
    while (a>0) {
      for (int i=0; i<a; i++) {
        w[i+b2]=(d[2*i+b1]-d[2*i+1+b1])*sqrt05;
        d[i+b2]=(d[2*i+b1]+d[2*i+1+b1])*sqrt05;
      }
      b1=b2;
      b2=b2+a;
      a/=2;
    }
    w[b2] = d[b1];

    for (int i=0; i<n-1; i++) w[i] = (int)(w[i]/scalingfactorin);
    if (w[n-1]>0) w[n-1] = (int)(w[n-1]/scalingfactorin+0.5);
    else w[n-1] = (int)(w[n-1]/scalingfactorin-0.5);
  }
}


/*

 AUECHO
 
 */


class AUECHO extends Shader {
  final int[] blends = {BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN};
  AUECHO() {
    name = "fxAUecho";
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("echo", FLOATVAL, 0.001, 1, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    params.add(new Param ("decay", FLOATVAL, 0.001, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("blend mode", INTVAL, 0, this.blends.length-1, new int[]{RANDOM }));


    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 4;
  }
  void apply() {
    canvas.beginDraw();
    if (boolean((int)params.get(0).getValue())) {
      canvas.colorMode(HSB);
      colorMode(HSB);
    } else {
      canvas.colorMode(RGB);
      colorMode(RGB);
    }
    canvas.loadPixels();

    float _delay = params.get(1).getValue();
    float decay = params.get(2).getValue();

    int delay = (int)(canvas.pixels.length * _delay);
    color[] history = new color[canvas.pixels.length];
    int blendMode =this.blends[(int)params.get(3).getValue()];
    for ( int i = 0, l = canvas.pixels.length; i<l; i++) {
      history[i] = canvas.pixels[i];
    }
    for ( int i = 0, l = canvas.pixels.length; i<l; i++) {
      int fromPos = i-delay < 0 ? l-abs(i-delay) : i-delay;
      color fromColor = history[fromPos];
      float r = red(fromColor) * decay;
      float g = green(fromColor) * decay;
      float b = blue(fromColor) * decay;
      color origColor = history[i];
      color toColor = color(
        r = r + red(origColor) > 255 ? r + red(origColor) - 255 : r + red(origColor), // simulate overflow ;)
        g = g + green(origColor) > 255 ? g + green(origColor) - 255 : g + green(origColor),
        b = b + blue(origColor) > 255 ? b + blue(origColor) - 255 : b + blue(origColor)  );

      //canvas.pixels[i] = history[i] = toColor;
      canvas.pixels[i] = history[i] = blendColor(origColor, toColor, blendMode);
    }
    canvas.updatePixels();
    if (boolean((int)params.get(0).getValue())) {
      canvas.colorMode(RGB);
      colorMode(RGB);
    }
    canvas.endDraw();
  }
}


/*

 SLITSCAN
 
 */


class SLITSCAN extends Shader {
  int[] fx;
  int[] fy;
  float[] phx;
  float[] phy;
  int[] sx, sy;
  boolean[] skipfx;
  boolean[] skipfy;
  boolean dox, doy;
  PImage buffer;
  float[][] ft = new float[2][32];
  //int depth; // number of octaves
  int  fxnum;
  int  fynum;
  SLITSCAN() {
    name = "fxSlitSscan";
    buffer = createImage(canvas.width, canvas.height, ARGB);

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 0;

    int s = (int)(log(min(buffer.width, buffer.height))/log(2));
    olds = s;
    params.add(new Param("x num", INTVAL, 1, s, new int[]{RANDOM}));
    params.add(new Param("y num", INTVAL, 1, s, new int[]{RANDOM}));

    for (int i=0; i<32; i++) {
      ft[0][i] = pow(2.0, i);
      ft[1][i] = 0.5*1.0/ft[0][i];
    }
  }
  int olds, oldfxnum, oldfynum;
  void apply() {
    canvas.beginDraw();
    canvas.colorMode(RGB);
    canvas.noStroke();
    colorMode(RGB);
    canvas.fill(255);
    buffer.resize(canvas.width, canvas.height);
    buffer = canvas.get(0, 0, canvas.width, canvas.height);

    //int s = buffer.width>buffer.height?buffer.height:buffer.width;
    //int s = min(buffer.width, buffer.height);
    //depth = (int)(log(s)/log(2));

    int s = (int)(log(min(buffer.width, buffer.height))/log(2));
    if (olds != s) {
      olds = s;
      changeParam(1, new Param("x num", INTVAL, 1, s, new int[]{RANDOM}));
      changeParam(2, new Param("y num", INTVAL, 1, s, new int[]{RANDOM}));
    }

    //depth = (int)params.get(1).getValue();
    //println(depth);
    fxnum = (int)random(params.get(1).getValue());
    fynum = (int)random(params.get(2).getValue());

    fx = new int[fxnum+1];
    fy = new int[fynum+1];
    sx = new int[fxnum+1];
    sy = new int[fynum+1];
    phx = new float[fxnum+1];
    phy = new float[fynum+1];
    skipfx = new boolean[fxnum+1];
    skipfy = new boolean[fynum+1];



    for (int i=0; i<fxnum; i++) {
      fx[i]=(int)random(6);
      phx[i] = random(1);
      skipfx[i] = random(1)<0.2;
      sx[i] = random(1)<0.2?-1:1;
    }
    for (int i=0; i<fynum; i++) {
      fy[i]=(int)random(6);
      phy[i] = random(1);
      skipfy[i] = random(1)<0.2;
      sy[i] = random(1)<0.2?-1:1;
    }


    //dox = random(1)<0.8;
    //doy = dox?random(1)<0.8:true;
    dox = true;
    doy = true;


    float v=0;
    for (int y=0; y<buffer.height; y++)
      for (int x=0; x<buffer.width; x++) {
        float iy = map(y, 0, buffer.height, 0, 1);

        v=0;
        if (doy) for (int i=0; i<fy.length; i++)
          if (!skipfy[i]) v+=sy[i]*getValue(fy[i], iy, i, phy[i]);

        float ry = 2*iy+v;
        float y2 = (3*buffer.height+ry * buffer.height/2)%buffer.height;

        float ix = map(x, 0, buffer.width, 0, 1);
        v=0;
        if (dox) for (int i=0; i<fx.length; i++)
          if (!skipfx[i]) v+=sx[i]*getValue(fx[i], ix, i, phx[i]);


        float rx = 2*ix+v;
        float x2 = (3*buffer.width+rx * buffer.width/2)%buffer.width;

        canvas.fill(buffer.get((int)x2, (int)y2));
        canvas.rect(x, y, 1, 1);
      }
    canvas.endDraw();
  }

  float getValue(int fun, float idx, int freq, float phase) {
    switch(fun) {
    case 0:
      return getSin(idx, freq, phase);
    case 1:
      return getSaw(idx, freq, phase);
    case 2:
      return getTriangle(idx, freq, phase);
    case 3:
      return getCutTriangle(idx, freq, phase);
    case 4:
      return getSquare(idx, freq, phase);
    case 5:
      return getNoise(idx, freq, phase);
    default:
      return getSin(idx, freq, phase);
    }
  }

  float getNoise(float idx, int freq, float phase) {
    return 2*ft[1][freq]*(noise((idx+phase)*ft[0][freq])-0.5);
  }

  float getSin(float idx, int freq, float phase) {
    float p = ft[0][freq];
    return ft[1][freq] * sin(idx*TWO_PI*p+phase*TWO_PI);
  }

  float getSaw(float idx, int freq, float phase) {
    float p = ft[0][freq];
    float rp = 2.0*ft[1][freq];
    float p2 = p*((idx+phase+ft[1][freq])%1.0);
    return rp*(p2-floor(p2)-0.5);
  }

  float getSquare(float idx, int freq, float phase) {
    float p = ft[0][freq];
    float rp = ft[1][freq];
    return (((idx*p)+phase)%1.0)<0.5?rp:-rp;
  }

  float getTriangle(float idx, int freq, float phase) {
    return 2*abs(getSaw(idx, freq, phase+0.5*ft[1][freq]))-ft[1][freq];
  }

  float getCutTriangle(float idx, int freq, float phase) {
    return constrain(getTriangle(idx, freq, phase), -ft[1][freq+1], ft[1][freq+1]);
  }
}

/*

 WAHWAH
 
 */


class WAHWAH extends Shader {
  float sequence, lfoskip, xn1, xn2, yn1, yn2, b0, b1, b2, a0, a1, a2, freqofs, freq, freqoff, startsequence, res, depth;
  float mCurRate = 0.4, skipcount = 0;
  int lfoskipsamples = 0;
  float frequency, omega, sn, cs, alpha;
  float in, out;
  float val;
  WAHWAH() {
    name = "fxWahWah";
    //params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("resolution", FLOATVAL, 1, 100, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    params.add(new Param ("depth", FLOATVAL, 0.0001, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("frequency offset", FLOATVAL, 0, 0.9, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("mCurRate", FLOATVAL, 0, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 4;
    // params.add(new Param ("blend mode", INTVAL, 0, this.blends.length-1, new int[]{RANDOM }));
  }
  void apply() {
    res = params.get(0).getValue();
    depth = params.get(1).getValue();
    freqofs = params.get(2).getValue();
    //res = 12.5
    //depth = 0.8;
    //freqofs = 0.9;

    freq = 1.5;
    startsequence = 0.2;
    lfoskip = freq * 2 * PI / mCurRate;
    skipcount = xn1 = xn2 = yn1 = yn2 = b0 = b1 = b2 = a0 = a1 = a2 = 0;
    sequence = startsequence;

    canvas.beginDraw();
    canvas.colorMode(RGB);
    canvas.loadPixels();
    float[] rgb = new float[3];
    for ( int i = 0, len = canvas.pixels.length; i < len; i++) {
      rgb[0] = red(canvas.pixels[i]);
      rgb[1] = green(canvas.pixels[i]);
      rgb[2] = blue(canvas.pixels[i]);
      for ( int ri = 0; ri < 3; ri++ ) {
        in = map(rgb[ri], 0, 255, 0, 1);
        frequency = (1+cos(skipcount * lfoskip + sequence ))/2;
        frequency = frequency * depth * (1-freqofs) + freqofs;
        frequency = exp((frequency - 1) * 6 );
        omega = PI * frequency;
        sn = sin(omega);
        cs = cos(omega);
        alpha = sn/(2*res);
        b0 = (1-cs) /2;
        b1 = 1 - cs;
        b2 = (1-cs)/2;
        a0 = 1 + alpha;
        a1 = -2 * cs;
        a2 = 1 - alpha;
        out = ( b0 * in + b1 * xn1 + b2 * xn2 - a1 * yn1 - a2 * yn2 ) / a0;
        xn2 = xn1;
        xn1 = in;
        yn2 = yn1;
        yn1 = out;
        rgb[ri] = map(out, 0, 1, 0, 255);
      }
      canvas.pixels[i] = color(rgb[0], rgb[1], rgb[2]);
    }

    canvas.updatePixels();
    canvas.endDraw();
  }
}


/*

 PHASER
 
 */


class PHASER extends Shader {
  //float samplerate = 92230.0; // try setting this to 44100.0 or 2048.5 for kicks
  float samplerate = 44100; // try setting this to 44100.0 or 2048.5 for kicks
  int mode;
  PHASER() {
    name ="fxPhaser";
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("frequency", FLOATVAL, 0.1, 40.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("depth", INTVAL, 1, 255, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("feedback", INTVAL, -100, 100, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("phase", FLOATVAL, 0, TWO_PI, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("stages", INTVAL, 1, 24, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("sample rate", FLOATVAL, 512, 92230, new int[]{TRIANG, SINE, RAMPUPDOWN }));

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 7;

    //params.add(new Param ("frequency offset", FLOATVAL, 0, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
  }
  /*
    mDepth = (int) map(knobZero, 0, 255, 255, 0);
   mFeedback = (int) map(knobOne, 0, 255, -100, 100);
   // enable these for some more fun :)
   if (mode == 1) {
   mSampleRate = map(knobTwo, 0, 255, 1, 512);
   mStages = (int) ( 2*map(knobThree, 0, 255, 1, 12));
   }
   */

  void apply() {
    mode = (int)params.get(0).getValue();
    float mFreq = params.get(1).getValue();

    int mDryWet = 255;


    int mDepth = (int)params.get(2).getValue();
    int mFeedback = (int)params.get(3).getValue();
    float mPhase = params.get(4).getValue();

    //these two are only changed if mode = 1
    int mStages = 2;
    float mSampleRate = samplerate;

    canvas.beginDraw();
    canvas.loadPixels();


    //constants
    float phaserlfoshape = 4.0;
    int lfoskipsamples = 20; //how many samples are processed before recomputing lfo
    int numStages = 24;


    //getParams
    /*
    Phaser Parameters
     mFreq       - Phaser's LFO frequency
     mPhase      - Phaser's LFO startsequence (radians), needed for stereo Phasers
     mDepth      - Phaser depth (0 - no depth, 255 - max depth)
     mStages     - Phaser stages (recomanded from 2 to 16-24, and EVEN NUMBER)
     mDryWet     - Dry/wet mix, (0 - dry, 128 - dry=wet, 255 - wet)
     mFeedback   - Phaser FeedBack (0 - no feedback, 100 = 100% Feedback,
     -100 = -100% FeedBack)
     */
    // enable these for some more fun :)
    if (mode == 1) {
      mStages = (int)params.get(5).getValue();
      mSampleRate = params.get(6).getValue();
    }
    //init
    float gain = 0, fbout = 0;
    float lfoskip = mFreq * 2 * PI / mSampleRate;
    float sequence = mPhase * PI / 180;

    float[] old = new float[mStages];
    for ( int j = 0; j < mStages; j++) {
      old[j] = 0.0;
    }

    /* EffectPhaser::ProcessBlock */
    int skipcount = 0;

    float[] rgb = new float[3];
    for ( int i = 0, l = canvas.pixels.length; i<l; i++ ) {
      color c = canvas.pixels[i];
      rgb[0] = map(red(c), 0, 255, 0, 1);
      rgb[1] = map(green(c), 0, 255, 0, 1);
      rgb[2] = map(blue(c), 0, 255, 0, 1);
      for ( int ci = 0; ci < 3; ci++) {
        float in = rgb[ci];
        float m = in + fbout * mFeedback / 100;
        if ( (( skipcount++) % lfoskipsamples ) == 0 ) { //recomopute lfo
          gain = (1.0 + cos(skipcount * lfoskip + sequence)) / 2.0; //compute sine between 0 and 1
          gain = exp(gain * phaserlfoshape) / exp(phaserlfoshape); // change lfo shape
          gain = 1.0 - gain / 255.0 * mDepth; // attenuate the lfo
        }
        //phasing routine
        for ( int j = 0; j<mStages; j++) {
          float tmp = old[j];
          old[j] = gain * tmp + m;
          m = tmp - gain * old[j];
        }
        fbout = m;
        rgb[ci] = (float) (( m * mDryWet + in * (255-mDryWet)) / 255);
      }
      color rc = color(
        map(rgb[0], 0, 1, 0, 255),
        map(rgb[1], 0, 1, 0, 255),
        map(rgb[2], 0, 1, 0, 255));
      canvas.pixels[i] = rc;
    }
    canvas.updatePixels();
    canvas.endDraw();
  }
}


/*

 ECHO
 
 */


class ECHO extends Shader {
  int mode = 0;
  PImage result;
  ECHO() {
    name = "fxEcho";
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("xp", INTVAL, 0, 100, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("yp", INTVAL, 0, 100, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 3;
    result = createImage(canvas.width, canvas.height, RGB);
  }
  void apply() {
    mode = (int)params.get(0).getValue();

    int xp = (int)params.get(1).getValue();
    int yp = (int)params.get(2).getValue();

    canvas.beginDraw();
    canvas.colorMode(RGB);
    colorMode(RGB);
    canvas.imageMode(CORNER);
    if (mode == 0) {
      canvas.image(auEcho(canvas, xp, yp), 0, 0);
    } else if (mode == 1) {
      canvas.image(auEchoWTF(canvas, xp, yp), 0, 0);
    }
    canvas.endDraw();
  }


  PImage auEcho(PImage img, int xp, int yp) {
    result.resize(img.width, img.height);
    float _delay = map(xp, 0, 100, 0.001, 1.0);
    float decay = map(yp, 0, 100, 0.0, 1.0);
    int delay = (int)(img.pixels.length * _delay);
    color[] history = new color[img.pixels.length];
    int blendMode = BLEND;
    img.loadPixels();
    result.loadPixels();
    for ( int i = 0, l = img.pixels.length; i<l; i++) {
      history[i] = img.pixels[i];
    }
    for ( int i = 0, l = img.pixels.length; i<l; i++) {
      int fromPos = i-delay < 0 ? l-abs(i-delay) : i-delay;
      color fromColor = history[fromPos];
      float r = red(fromColor) * decay;
      float g = green(fromColor) * decay;
      float b = blue(fromColor) * decay;
      color origColor = history[i];
      color toColor = color(
        r + red(origColor),
        g + green(origColor),
        b + blue(origColor) );

      result.pixels[i] = history[i] = blendColor(origColor, toColor, blendMode);
    }
    return result;
  }

  PImage auEchoWTF(PImage img, int xp, int yp) {
    result.resize(img.width, img.height);

    float delay = map(xp, 0, 100, 0.001, 5);
    float decay = map(yp, 0, 100, 0.0, 5.0);
    int histPos = 0;
    int histLen = img.pixels.length*3;
    float[] history = new float[histLen*3];

    img.loadPixels();
    result.loadPixels();
    float ibuf = 0.0, obuf = 0.0;
    float[] rgb = new float[3];
    for ( int i = 0, l = img.pixels.length; i<l; i++, histPos++) {
      color c = img.pixels[i];
      rgb[0] = map(red(c), 0, 255, 0, 1);
      rgb[1] = map(green(c), 0, 255, 0, 1);
      rgb[2] = map(blue(c), 0, 255, 0, 1);
      history[i] = rgb[0];
      history[i+1] = rgb[1];
      history[i+2] = rgb[2];
    }
    for ( int i = 0, l = img.pixels.length; i<l; i++, histPos++) {
      color c = img.pixels[i];
      rgb[0] = map(red(c), 0, 255, 0, 1);
      rgb[1] = map(green(c), 0, 255, 0, 1);
      rgb[2] = map(blue(c), 0, 255, 0, 1);

      if ( histPos == histLen ) histPos = 0;
      for ( int ri = 0; ri < 3; ri++ ) {
        history[histPos+ri] = rgb[ri] = rgb[ri] + history[histPos+ri] * decay;
      }
      color out = color(
        (int)map(rgb[0], 0, 1, 0, 255),
        (int)map(rgb[1], 0, 1, 0, 255),
        (int)map(rgb[2], 0, 1, 0, 255));
      result.pixels[i] = out;
    }

    return result;
  }
}

/*

 DARKER
 
 */

class DARKER extends Shader {
  float thresh = 127;
  float darken = 150;
  int mode = 1;
  int bangCount = 0;
  DARKER() {
    name = "fxDarker";
    params.add(new Param("count", INTVAL, 1, 4, new int[]{TRIANG, SINE, RAMPUPDOWN, TAN, TANINVERSE}));
    params.add(new Param("threshold", INTVAL, 60, 180, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("darken", INTVAL, 140, 220, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));

    //thresh = int(random(60, 180));
    //darken = int(random(140, 220));
  }

  void apply() {
    // bright = knobZero;
    int count = int(params.get(0).getValue());
    darken = int(params.get(1).getValue());
    thresh = int(params.get(2).getValue());
    mode = int(params.get(3).getValue());

    canvas.beginDraw();
    canvas.colorMode(HSB);
    colorMode(HSB);
    canvas.loadPixels();

    if (mode == 0) {
      for (int h = 1; h < count+1; h++) {
        for (int i = 0; i < canvas.width*canvas.height; i++) {
          float hue = hue(canvas.pixels[i]);
          float sat = saturation(canvas.pixels[i]);
          float bright = brightness(canvas.pixels[i]);

          if (bright > thresh/h) {
            bright -= darken/h;
            constrain(bright, 0, 255);
          }
          color c = color(hue, sat, bright);
          canvas.pixels[i] = c;
        }
      }
    } else if (mode == 1) {
      for (int h = 1; h < count+1; h++) {
        for (int i = 0; i < canvas.width*canvas.height; i++) {
          float hue = hue(canvas.pixels[i]);
          float sat = saturation(canvas.pixels[i]);
          float bright = brightness(canvas.pixels[i]);

          if (bright < thresh/h) {
            bright -= darken/h;
            constrain(bright, 0, 255);
          }
          color c = color(hue, sat, bright);
          canvas.pixels[i] = c;
        }
      }
    }
    canvas.updatePixels();
    canvas.endDraw();
    colorMode(RGB);
  }
}


/*

 BRIGHTER
 
 */

class BRIGHTER extends Shader {
  float thresh = 120;
  float thresh2 = 150;
  float brighten = 180;
  float speed;
  BRIGHTER() {
    name = "fxBrighter";
    params.add(new Param("threshold", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("threshold 2", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("brighten", INTVAL, 0, 255, new int[]{TRIANG, SINE, RAMPUPDOWN }));
  }

  void apply() {
    brighten = int(params.get(2).getValue());
    thresh2 = int(params.get(1).getValue());
    thresh = int(params.get(0).getValue());

    canvas.beginDraw();
    canvas.colorMode(HSB);
    colorMode(HSB);
    canvas.loadPixels();
    for (int i = 0; i < canvas.width*canvas.height; i++) {
      float hue = hue(canvas.pixels[i]);
      float sat = saturation(canvas.pixels[i]);
      float bright = brightness(canvas.pixels[i]);
      if (bright < thresh && bright > thresh2) {
        bright += brighten;
        constrain(bright, 0, 255);
      }
      color c = color(hue, sat, bright);
      canvas.pixels[i] = c;
    }
    canvas.colorMode(RGB);
    canvas.updatePixels();
    canvas.endDraw();
    colorMode(RGB);
  }
}

/*

 AMPLIFY
 
 */


//13
class AMPLIFY extends Shader {
  int spawnT;
  float h, s, b;
  AMPLIFY() {
    name = "fxAmplify";
    params.add(new Param("hue / red", FLOATVAL, -255, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("saturation / green", FLOATVAL, -255, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("brightness / blue", FLOATVAL, -255, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("RGB / HSB", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
  }
  void apply() {
    canvas.beginDraw();
    if (params.get(3).getValue() > 0) colorMode(HSB);
    else {
      colorMode(RGB);
    }
    canvas.loadPixels();
    for (int i = 0; i < canvas.width*canvas.height; i++) {
      float h = hue(canvas.pixels[i]);
      float s = saturation(canvas.pixels[i]);
      float b = brightness(canvas.pixels[i]);
      canvas.pixels[i] = color(h+params.get(0).getValue(), s+params.get(1).getValue(), b+params.get(2).getValue());
    }
    canvas.updatePixels();
    canvas.endDraw();
    colorMode(RGB);
  }
}

/*

 BROKENCOLORROT
 
 */


class BROKENCOLORROT extends Shader {
  int spawnT;
  float h, s, b;
  BROKENCOLORROT() {
    name = "fxBrokenColorRot";

    params.add(new Param("hue", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("saturation", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("brightness", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));


    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 4;
  }
  void apply() {
    canvas.beginDraw();
    if (params.get(3).getValue() > 0) colorMode(HSB);
    else {
      colorMode(RGB);
    }
    canvas.loadPixels();
    float h = params.get(0).getValue();
    float s = params.get(1).getValue();
    float b = params.get(2).getValue();
    for (int i = 0; i < canvas.width*canvas.height; i++) {
      h = hue(canvas.pixels[i])+h;
      if (h > 255) h -= 255;
      s = saturation(canvas.pixels[i])+s;
      if (s > 255) s -= 255;
      b = brightness(canvas.pixels[i])+b;
      if (b > 255) b -= 255;
      canvas.pixels[i] = color(h, s, b);
    }
    canvas.updatePixels();
    canvas.endDraw();
    colorMode(RGB);
  }
}



/*

 POSTERIZE
 
 */

class POSTER extends Shader {
  int levels = 2;
  POSTER() {
    name = "fxPosterize";
    params.add(new Param("levels", INTVAL, 2, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }
  void apply() {
    levels = (int)params.get(0).getValue();
    canvas.beginDraw();
    canvas.filter(POSTERIZE, levels);
    canvas.endDraw();
  }
}

/*

 DUAL
 
 */

class DUAL extends Shader {
  PImage buffer;
  int dualColor;
  int dirx = 1;
  int diry = 1;
  DUAL() {
    name = "fxDual";
    params.add(new Param("dual color", INTVAL, 2000000, 15000000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("flip direction", INTVAL, 0, 3, new int[]{RANDOM}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 3;

    buffer = createImage(canvas.width, canvas.height, ARGB);
  }
  void apply() {
    switch((int)params.get(1).getValue()) {
      case(0):
      dirx = 1;
      diry = 1;
      break;
      case(1):
      dirx = -1;
      diry = 1;
      break;
      case(2):
      dirx = 1;
      diry = -1;
      break;
      case(3):
      dirx = -1;
      diry = -1;
      break;
    }
    dualColor = (int)params.get(0).getValue();
    buffer.resize(canvas.width, canvas.height);
    canvas.beginDraw();
    canvas.imageMode(CORNER);
    canvas.loadPixels();
    buffer.loadPixels();
    if ((int)params.get(2).getValue() > 0) {
      for (int i = 0; i < canvas.width*canvas.height; i++) {
        buffer.pixels[i] = canvas.pixels[i]+dualColor;
      }
    } else {
      for (int i = 0; i < canvas.width*canvas.height; i++) {
        buffer.pixels[i] = canvas.pixels[i]+dualColor;
        buffer.pixels[i] = buffer.pixels[i]+i/10;
      }
    }
    buffer.updatePixels();
    canvas.updatePixels();
    canvas.pushMatrix();
    canvas.scale(dirx, diry);
    canvas.image(buffer, 0, 0, dirx * canvas.width, diry * canvas.height);
    canvas.popMatrix();
    canvas.endDraw();
  }
}

/*

 GRAUZONE
 
 */

class GRAUZONE extends Shader {
  int nFrames = 20;
  int iWrite = 0, iRead = 1;
  PImage[] buffer;
  PGraphics grauz;
  GRAUZONE() {
    name = "fxGrauzone";
    params.add(new Param("delay (in frames)", INTVAL, 3, 100, new int[]{RANDOM}));
    nFrames = (int)params.get(0).getValue();
    buffer = new PImage[nFrames];
  }
  int nFramesPrev;
  void apply() {
    nFramesPrev = nFrames;
    if (nFramesPrev != (int)params.get(0).getValue()) {
      iWrite = 0;
      iRead = 1;
      int nFramesOld = nFrames;
      nFrames = (int)params.get(0).getValue();
      if (nFrames > nFramesOld) {
        buffer = new PImage[nFrames];
      }
    }

    buffer[iWrite] = canvas.get();
    grauz = createGraphics(canvas.width, canvas.height);
    grauz.beginDraw();
    //  grauz.resize(canvas.width, canvas.height);
    buffer[iWrite] = canvas.get();
    if (buffer[iRead] != null) {
      grauz.tint(255, 127);
      buffer[iRead].filter(INVERT);
      grauz.image(buffer[iRead], 0, 0, canvas.width, canvas.height);
      grauz.tint(255, 255);
    }
    grauz.endDraw();
    canvas.beginDraw();
    canvas.imageMode(CORNER);
    canvas.image(grauz, 0, 0, canvas.width, canvas.height);
    canvas.endDraw();


    iWrite++;
    iRead++;
    if (iRead >= nFrames-1) {
      iRead = 0;
    }
    if (iWrite >= nFrames-1) {
      iWrite = 0;
    }
  }
}

/*

 COPYZOOM
 
 */

class COPYZOOM extends Shader {
  int coprx, copry, coprw, coprh;
  PImage buffer;
  COPYZOOM() {
    name = "fxCopyZoom";
    params.add(new Param("w", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("x", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("y", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    buffer = createImage(canvas.width, canvas.height, ARGB);
  }
  void apply() {
    canvas.imageMode(CORNER);
    if (buffer.width != canvas.width || buffer.height != canvas.height) buffer.resize(canvas.width, canvas.height);
    buffer = canvas.get();
    coprw = int(map(params.get(0).getValue(), 0, 1, 1, canvas.width));
    coprh = int(map(params.get(1).getValue(), 0, 1, 1, canvas.height));
    coprx = int(map(params.get(2).getValue(), 0, 1, 0, canvas.width-coprw));
    copry = int(map(params.get(3).getValue(), 0, 1, 0, canvas.height-coprh));
    canvas.beginDraw();
    buffer.copy(coprx, copry, coprw, coprh, 0, 0, canvas.width, canvas.height);
    canvas.image(buffer, 0, 0, canvas.width, canvas.height);
    canvas.endDraw();
  }
}

/*

 SUBTLESORT
 
 */

class SUBTLESORT extends Shader {
  int direction = 0;
  int mode = 0;
  int c;
  //int count = int(random(777));
  color col;
  final static int RED = 0;
  final static int GREEN = 1;
  final static int BLUE = 2;
  final static int HUE = 3;
  final static int SATURATION = 4;
  final static int BRIGHTNESS = 5;
  final static int NRED = 6;
  final static int NGREEN = 7;
  final static int NBLUE = 8;
  final static int NHUE = 9;
  final static int NSATURATION = 10;
  final static int NBRIGHTNESS = 11;

  // channels for depth: RED, GREEN, BLUE, HUE, SATURATION, BRIGHTNESS, NRED, NGREEN, NBLUE, NHUE, NSATURATION, NBRIGHTNESS.
  int channel = BRIGHTNESS;
  // channel weight.
  float channel_weight = .2;
  //
  SUBTLESORT() {
    name ="fxSubtleSort";
    params.add(new Param ("channel weight", FLOATVAL, 0.001, 20, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("channel", INTVAL, 0, 6, new int[]{RANDOM}));
    //params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUARE, RANDOM}));


    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 3;
  }

  void apply() {
    channel_weight = map(renderSize, 4, 15000, 0, 1.5)*params.get(0).getValue();
    channel = (int)params.get(1).getValue();
    direction = (int)params.get(2).getValue();
    mode = (int)params.get(3).getValue();
    canvas.beginDraw();
    canvas.noStroke();

    //if (direction == 0) {
    for (int i = 0; i < canvas.width; i++) {
      for (int j = 0; j < canvas.height; j++) {
        c = i+(j*canvas.width);
        col = canvas.pixels[c];
        canvas.fill(col);
        canvas.rect(i, j+(getChannel(col)), 1, getChannel(col));
      }
    }
    //}
    /*else if (direction == 1) {
     for (int i = 0; i < canvas.width; i++) {
     for (int j = 0; j < canvas.height; j++) {
     c = i+(j*canvas.width);
     col = canvas.pixels[c];
     canvas.fill(col);
     canvas.rect(i, j-(getChannel(col)*mode), 1, -getChannel(col));
     }
     }
     } else if (direction == 2) {
     for (int i = 0; i < canvas.width; i++) {
     for (int j = 0; j < canvas.height; j++) {
     c = i+(j*canvas.width);
     col = canvas.pixels[c];
     canvas.fill(col);
     canvas.rect(i-(getChannel(col)*mode), j, -getChannel(col), 1);
     }
     }
     } else if (direction == 3) {
     for (int i = 0; i < canvas.width; i++) {
     for (int j = 0; j < canvas.height; j++) {
     c = i+(j*canvas.width);
     col = canvas.pixels[c];
     canvas.fill(col);
     canvas.rect(i+(getChannel(col)), j, getChannel(col), 1);
     }
     }
     }*/

    canvas.endDraw();
  }


  float getChannel(color c) {
    int ch = channel;
    float cc;

    switch(ch) {
    case RED:
      cc = red(c);
      break;
    case GREEN:
      cc = green(c);
      break;
    case BLUE:
      cc = blue(c);
      break;
    case HUE:
      cc = hue(c);
      break;
    case SATURATION:
      cc = saturation(c);
      break;
    default:
      cc= brightness(c);
      break;
    }

    return channel_weight * (channel>5?255-cc:cc);
  }
}



/*

 SCANKER
 
 */



class SCANKER extends Shader {
  int mode;
  SCANKER() {
    name = "fxScanker";
    params.add(new Param ("detail level 1", FLOATVAL, 0.001, 1000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("detail level 2", FLOATVAL, -50, 50, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("detail level 3", FLOATVAL, -5, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));

    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 4;
  }
  void apply() {
    mode = (int)params.get(3).getValue();
    canvas.beginDraw();
    canvas.loadPixels();
    float factor = params.get(0).getValue() + params.get(1).getValue() + params.get(2).getValue();
    if (mode == 0) {
      for (int i = 0; i < canvas.width*canvas.height; i++) {
        //     canvas.pixels[i] = canvas.pixels[i]+((i/1000)*scankMulti);
        canvas.pixels[i] = canvas.pixels[i]-int(map(i, 0, canvas.width*canvas.height, 0, source.width*source.height)/10*factor);
      }
    } else if (mode == 1) {
      for (int i = 0; i < canvas.width*canvas.height; i++) {
        //     canvas.pixels[i] = canvas.pixels[i]+((i/1000)*scankMulti);
        canvas.pixels[i] = canvas.pixels[i]+int(map(i, 0, canvas.width*canvas.height, 0, source.width*source.height)/10*factor);
      }
    }
    canvas.updatePixels();
    canvas.endDraw();
  }
}


/*

 MASK
 
 */


class MASK extends Shader {
  int backgroundLayer, maskLayer, bandwidth, val, chan, shaderListLength, invert, blend_mode;
  boolean do_blend;
  PImage mask;
  PImage background;
  PImage foreground;
  MASK() {
    name = "mask";
    shaderListLength = gui.shaderList.size();
    params.add(new Param("target layer", INTVAL, 0, shaderListLength-2, new int[]{RANDOM}));
    params.add(new Param("mask layer", INTVAL, 0, shaderListLength-1, new int[]{RANDOM}));
    params.add(new Param("invert", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("bandwidth", INTVAL, 0, 255, new int[]{TRIANG, SINE}));
    params.add(new Param("value", INTVAL, 1, 255, new int[]{TRIANG, SINE}));
    params.add(new Param("channel (R/G/B/H/S/V", INTVAL, 0, 5, new int[]{RANDOM}));
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM}));
    params.add(new Param("blend mode", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    background = createImage(canvas.width, canvas.height, ARGB);
    mask = createImage(canvas.width, canvas.height, ARGB);
    foreground = createImage(canvas.width, canvas.height, ARGB);
  }

  void apply() {
    if (shaderListLength != gui.shaderList.size()) {
      shaderListLength = gui.shaderList.size();
      changeParam(0, new Param("target layer", INTVAL, 0, shaderListLength-2, new int[]{RANDOM}));
      changeParam(1, new Param("mask layer", INTVAL, 0, shaderListLength-1, new int[]{RANDOM}));
    }
    if (mask.width != canvas.width || mask.height != canvas.height) mask.resize(canvas.width, canvas.height);
    if (background.width != canvas.width || background.height != canvas.height) background.resize(canvas.width, canvas.height);
    if (foreground.width != canvas.width || foreground.height != canvas.height) foreground.resize(canvas.width, canvas.height);


    backgroundLayer = (int)params.get(0).getValue();
    maskLayer = (int)params.get(1).getValue();
    invert = (int)params.get(2).getValue();
    bandwidth = (int)params.get(3).getValue();
    val = (int)params.get(4).getValue();
    chan = (int)params.get(5).getValue();
    do_blend = boolean((int)params.get(6).getValue());
    blend_mode = blends[(int)params.get(7).getValue()];

    if (backgroundLayer <= 0) {
      background.resize(source.width, source.height);
      background = source.get();
      background.resize(canvas.width, canvas.height);
    } else background = gui.shaderList.get(backgroundLayer-1).result.get();
    if (maskLayer <= 0) {
      mask.resize(source.width, source.height);
      mask = source.get();
      mask.resize(canvas.width, canvas.height);
    } else mask = gui.shaderList.get(maskLayer-1).result.get();

    canvas.beginDraw();
    canvas.imageMode(CORNER);
    mask.loadPixels();
    foreground.loadPixels();
    canvas.loadPixels();
    if (boolean(invert)) {
      for (int i = 0; i < canvas.width * canvas.height; i++) {
        if (!thresh(color(mask.pixels[i])))
          foreground.pixels[i] = canvas.pixels[i];
        else
          foreground.pixels[i] = background.pixels[i];
      }
    } else {
      for (int i = 0; i < canvas.width * canvas.height; i++) {
        if (thresh(color(mask.pixels[i])))
          foreground.pixels[i] = canvas.pixels[i];
        else
          foreground.pixels[i] = background.pixels[i];
      }
    }
    canvas.updatePixels();
    foreground.updatePixels();
    mask.updatePixels();
    if (do_blend)
      canvas.blend(foreground, 0, 0, foreground.width, foreground.height, 0, 0, canvas.width, canvas.height, blend_mode);
    else
      canvas.image(foreground, 0, 0, canvas.width, canvas.height);
    canvas.endDraw();
  }

  boolean thresh(color c) {
    switch(chan) {
      case(0):
      if (red(c) > val - bandwidth && red(c) < val + bandwidth)
        return(true);
      else
        return(false);
      case(1):
      if (green(c) > val - bandwidth && green(c) < val + bandwidth)
        return(true);
      else
        return(false);
      case(2):
      if (blue(c) > val - bandwidth && blue(c) < val + bandwidth)
        return(true);
      else
        return(false);
      case(3):
      if (hue(c) > val - bandwidth && hue(c) < val + bandwidth)
        return(true);
      else
        return(false);
      case(4):
      if (saturation(c) > val - bandwidth && saturation(c) < val + bandwidth)
        return(true);
      else
        return(false);
      case(5):
      if (brightness(c) > val - bandwidth && brightness(c) < val + bandwidth)
        return(true);
      else
        return(false);
    }
    return(false);
  }
}



class DRAWSTROKES extends Shader {

  int stat_type = ABSDIST2; // type of diff calculation: fast: ABSDIST, DIST, slow: HUE, SATURATION, BRIGHTNESS
  int stroke_len = 3; // length of the stroke, values: 1 and above
  int angles_no = 30; // number of directions stroke can be drew, 2 and above
  int segments = 500; // number of segments of single thread
  float stroke_width = 1; // width of the stroke, 0.5 - 3
  int stroke_alpha = 100; // alpha channel of the stroke: 30 - 200

  color background_color = color(255, 255, 255); // RGB

  boolean interactive = false;
  int max_display_size = 800; // viewing window size (regardless image size)

  int len;
  // working buffer
  PGraphics buffer;

  int currx, curry;
  int[] sintab, costab;
  int sqwidth;

  int iterations;

  int calcDiff(PImage img1, PImage img2) {
    int err = 0;
    for (int i=0; i<img1.pixels.length; i++)
      err += getStat(img1.pixels[i], img2.pixels[i]);
    return err;
  }

  DRAWSTROKES() {
    name = "fxDrawStrokes";
    params.add(new Param ("stat type", INTVAL, 0, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE})); // 4 und 5 sind mit abstand die schnellsten
    params.add(new Param ("stroke length", INTVAL, 1, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("amount of angles", INTVAL, 2, 30, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("amount of segments", INTVAL, 50, 1500, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("stroke width", FLOATVAL, 0.5, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("stroke transparency", INTVAL, 30, 220, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("iterations", INTVAL, 1, 500, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));

    len = (renderer.width<renderer.height?renderer.width:renderer.height)/3;

    buffer = createGraphics(renderer.width, renderer.height);
    buffer.smooth(2);
    buffer.beginDraw();
    buffer.strokeWeight(stroke_width);
    buffer.noFill();
    buffer.endDraw();

    rw = renderer.width;
    rh = renderer.height;
  }


  int rw, rh;
  void apply() {
    stat_type = (int)params.get(0).getValue();
    stroke_len = (int)params.get(1).getValue();
    angles_no = (int)params.get(2).getValue();
    segments = (int)params.get(3).getValue();
    stroke_width = params.get(4).getValue();
    stroke_alpha = (int)params.get(5).getValue();
    iterations = (int)params.get(6).getValue();

    if (rw != canvas.width || rh != canvas.height) { //doesnt account image swap
      len = (canvas.width<canvas.height?canvas.width:canvas.height)/3;
      rw = canvas.width;
      rh = canvas.height;
      PGraphics save = createGraphics(canvas.width, canvas.height);
      save.beginDraw();
      save.image(buffer, 0, 0, save.width, save.height);
      save.endDraw();
      buffer.setSize(canvas.width, canvas.height);
      buffer.beginDraw();
      buffer.image(save, 0, 0, buffer.width, buffer.height); // ????
      buffer.endDraw();
    }

    for (int j = 0; j < iterations; j++) {

      currx = (int)random(canvas.width);
      curry = (int)random(canvas.height);
      sintab = new int[angles_no];
      costab = new int[angles_no];

      for (int i=0; i<angles_no; i++) {
        sintab[i] = (int)(stroke_len * sin(TWO_PI*i/(float)angles_no));
        costab[i] = (int)(stroke_len * cos(TWO_PI*i/(float)angles_no));
      }

      sqwidth = stroke_len * 2 + 4;

      buffer.beginDraw();
      buffer.strokeWeight(stroke_width);
      //draw whole segment using current color
      buffer.stroke(canvas.get(currx, curry), stroke_alpha);

      for (int iter=0; iter<segments; iter++) {
        // corners of square containing new strokes
        int corx = currx-stroke_len-2;
        int cory = curry-stroke_len-2;

        // take square from image and current screen
        PImage imgpart = canvas.get(corx, cory, sqwidth, sqwidth);
        PImage mypart = buffer.get(corx, cory, sqwidth, sqwidth);
        imgpart.loadPixels();
        mypart.loadPixels();

        // calc current diff
        float localerr = calcDiff(imgpart, mypart);

        // chosen stroke will be here
        PImage destpart = null;
        int _nx=currx, _ny=curry;

        // start with random angle
        int i = (int)random(angles_no);
        int iterangles = angles_no;

        while (iterangles-- > 0) {
          // take end points
          int nx = currx + costab[i];
          int ny = curry + sintab[i];

          // if not out of the screen
          if (nx>=0 && nx<canvas.width-1 && ny>=0 && ny<canvas.height-1) {
            // clean region and draw line
            buffer.image(mypart, corx, cory);
            buffer.line(currx, curry, nx, ny);

            // take region with line and calc diff
            PImage curr = buffer.get(corx, cory, sqwidth, sqwidth);
            curr.loadPixels();
            int currerr = calcDiff(imgpart, curr);

            // if better, remember this region and line endpoint
            if (currerr < localerr) {
              destpart = curr;
              _nx = nx;
              _ny = ny;
              localerr = currerr;
            }
          }

          // next angle
          i = (i+1)%angles_no;
        }

        // if we have new stroke, draw it
        if (destpart != null) {
          buffer.image(destpart, corx, cory);
          currx = _nx;
          curry = _ny;
        } else {
          break; // skip
        }
      }

      buffer.endDraw();
    }
    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }

  final static int DIST = 0;
  final static int HUE = 1;
  final static int BRIGHTNESS = 2;
  final static int SATURATION = 3;
  final static int ABSDIST = 4;
  final static int ABSDIST2 = 5;

  final float getStat(color c1, color c2) {
    switch(stat_type) {
    case HUE:
      abs(hue(c1)-hue(c2));
    case BRIGHTNESS:
      abs(brightness(c1)-brightness(c2));
    case SATURATION:
      abs(saturation(c1)-saturation(c2));
    case ABSDIST:
      return abs(red(c1)-red(c2))+abs(green(c1)-green(c2))+abs(blue(c1)-blue(c2));
    case ABSDIST2:
      return abs( (red(c1)+blue(c1)+green(c1)) - (red(c2)+blue(c2)+green(c2)) );
    default:
      return sq(red(c1)-red(c2)) + sq(green(c1)-green(c2)) + sq(blue(c1)-blue(c2));
    }
  }
}

/*

 DRAWGENERATIVE
 
 */

class DRAWGENERATIVE extends Shader {

  // choose channel
  int channel = HUE;

  // run, after 30 iterations result will be saved automatically
  // or press SPACE


  // channels to work with
  final static int RED = 0;
  final static int GREEN = 1;
  final static int BLUE = 2;
  final static int HUE = 3;
  final static int SATURATION = 4;
  final static int BRIGHTNESS = 5;
  final static int NRED = 6;
  final static int NGREEN = 7;
  final static int NBLUE = 8;
  final static int NHUE = 9;
  final static int NSATURATION = 10;
  final static int NBRIGHTNESS = 11;

  int n=2000;
  float [] cx=new float[n];
  float [] cy=new float[n];

  int len;

  // working buffer
  PGraphics buffer;


  int tick = 0;


  DRAWGENERATIVE() {
    name = "fxDrawGenerative";
    params.add(new Param ("stroke width", FLOATVAL, 0.3, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE})); // 4 und 5 sind mit abstand die schnellsten
    buffer = createGraphics(renderer.width, renderer.height);
    buffer.noFill();
    buffer.beginDraw();
    //buffer.background(0); //ENABLE THIS TO DRAW FROM BLANK
    buffer.endDraw();
    rw = canvas.width;
    rh = canvas.height;
    len = (canvas.width<canvas.height?canvas.width:canvas.height)/6;

    for (int i=0; i<n; i++) {
      cx[i]=random(canvas.width);
      cy[i]=random(canvas.height);
    }
  }


  int rw, rh;
  void apply() {
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      PGraphics save = createGraphics(canvas.width, canvas.height);
      save.beginDraw();
      save.image(buffer, 0, 0, save.width, save.height);
      save.endDraw();
      buffer.setSize(canvas.width, canvas.height);
      buffer.beginDraw();
      buffer.image(save, 0, 0, buffer.width, buffer.height);
      buffer.endDraw();
    }
    buffer.beginDraw();
    buffer.strokeWeight(params.get(0).getValue());
    for (int i=1; i<n; i++) {
      color c = canvas.get((int)cx[i], (int)cy[i]);
      buffer.stroke(c);
      buffer.point(cx[i], cy[i]);
      // you can choose channels: red(c), blue(c), green(c), hue(c), saturation(c) or brightness(c)
      cy[i]+=sin(map(getChannel(c), 0, 255, 0, TWO_PI));
      cx[i]+=cos(map(getChannel(c), 0, 255, 0, TWO_PI));
    }
    if (frameCount>len) {
      frameCount=0;
      //println("iteration: " + tick++);
      for (int i=0; i<n; i++) {
        cx[i]=random(canvas.width); //same problem as in slitscan here
        cy[i]=random(canvas.height);
      }
    }
    buffer.endDraw();
    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }


  float getChannel(color c) {
    int ch = channel>5?channel-6:channel;
    float cc;

    switch(ch) {
    case RED:
      cc = red(c);
      break;
    case GREEN:
      cc = green(c);
      break;
    case BLUE:
      cc = blue(c);
      break;
    case HUE:
      cc = hue(c);
      break;
    case SATURATION:
      cc = saturation(c);
      break;
    default:
      cc= brightness(c);
      break;
    }

    return channel>5?255-cc:cc;
  }
}

/*

 PIXELDRIFTER
 
 */


class PIXELDRIFTER extends Shader {
  int channel = HUE; // channel data used for shift
  float channel_off = 60; // channel value offset
  int iteration_no = 50; // number of iterations 1-100
  int max_iter = iteration_no;
  int direction = UP; // UP, DOWN, LEFT, RIGHT
  boolean automate_channel_offset = false; // if true, change channel_offset every iteration
  float scale = 0.03; // 0.01 - 0.1, step size (0.01: 2px, 0.1:25px)
  float feedback = 0.9; // 0.9 - 0.999 ; blend ratio with original image

  boolean do_blend = false; // blend image after process
  int blend_mode = OVERLAY; // blend type

  // working buffer
  PGraphics buffer;

  // image
  PImage  imgb;
  PImage img;
  String sessionid;
  float acho_step;
  int rw, rh;

  PIXELDRIFTER() {
    name = "fxPixelDrifter";
    params.add(new Param ("channel offset", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("iterations", INTVAL, 1, 100, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("step size", FLOATVAL, 0.01, 0.1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("feedback", FLOATVAL, 0.1, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("direction", INTVAL, 37, 40, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("blend mode", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param ("channel", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
    params.add(new Param ("automate channel offset", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
    rw = canvas.width;
    rh = canvas.height;

    img = createImage(rw, rh, ARGB);

    buffer = createGraphics(rw, rh);
    buffer.beginDraw();
    buffer.noStroke();
    buffer.smooth(8);
    buffer.background(0);
    buffer.image(img, 0, 0);
    buffer.endDraw();

    scale = abs(scale);
  }

  void apply() {

    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      img.resize(rw, rh);
      buffer = createGraphics(rw, rh);
    }
    channel_off = map(params.get(0).getValue(), 0, 1, 10, max(rw, rh));
    iteration_no = (int)params.get(1).getValue();
    scale = params.get(2).getValue();
    feedback = params.get(3).getValue();
    direction = (int)params.get(4).getValue();
    do_blend = boolean((int)params.get(5).getValue());
    blend_mode = blends[(int)params.get(6).getValue()];
    channel = (int)params.get(7).getValue() == 1 ? HUE : RGB;
    automate_channel_offset = boolean((int)params.get(8).getValue());
    acho_step = 256.0 / iteration_no;

    img = canvas.get();

    //    if (iteration_no>0) {
    for (int i = 0; i < iteration_no; i++) {
      processImage();
    }
    //iteration_no--;
    //iteration_no=(iteration_no==0)?max_iter:iteration_no;
    //    if (iteration_no == 0)
    //      iteration_no = 50;
    //    }
    //    } else {

    canvas.beginDraw();
    if (do_blend) {
      canvas.blend(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height, blend_mode);
    } else {
      canvas.image(buffer, canvas.width/2, canvas.height/2, canvas.width, canvas.height);
    }
    canvas.endDraw();
    //    canvas.image(img, 0, 0, renderer.width, renderer.height);
    //     noLoop();
    //    }
  }

  void processImage() {
    buffer.beginDraw();
    for (int x=0; x<img.width; x++) {
      for (int y=0; y<img.height; y++) {
        color c = img.get(x, y);
        color c2;
        if (direction == UP || direction == DOWN) {
          c2 = img.get(x, ((int)(y+img.height+( (channel_off+getChannel(c, channel))%255 )*(direction==DOWN?-1.0:1.0)*scale))%img.height);
        } else {
          c2 = img.get(((int)(x+img.width+( (channel_off+getChannel(c, channel))%255)*(direction==RIGHT?-1.0:1.0)*scale))%img.width, y);
        }
        buffer.set(x, y, lerpColor(c, c2, feedback) );
      }
    }
    buffer.endDraw();
    //canvas.image(buffer, 0, 0, width, height);
    img = buffer.get();
    if (automate_channel_offset) channel_off += acho_step;
  }
  // ALL Channels, Nxxx stand for negative (255-value)
  // channels to work with
  final static int RED = 0;
  final static int GREEN = 1;
  final static int BLUE = 2;
  final static int HUE = 3;
  final static int SATURATION = 4;
  final static int BRIGHTNESS = 5;
  final static int NRED = 6;
  final static int NGREEN = 7;
  final static int NBLUE = 8;
  final static int NHUE = 9;
  final static int NSATURATION = 10;
  final static int NBRIGHTNESS = 11;

  float getChannel(color c, int channel) {
    int ch = channel>5?channel-6:channel;
    float cc;

    switch(ch) {
    case RED:
      cc = red(c);
      break;
    case GREEN:
      cc = green(c);
      break;
    case BLUE:
      cc = blue(c);
      break;
    case HUE:
      cc = hue(c);
      break;
    case SATURATION:
      cc = saturation(c);
      break;
    default:
      cc= brightness(c);
      break;
    }

    return channel>5?255-cc:cc;
  }
}

/*

 DRIPDRIP
 
 */

class DRIPDRIP extends Shader {
  int maxsteps = 50;  //maximum fade length in px
  int minsteps = 2;   //minimum fade length in px
  Boolean brightmode = false;  //if enabled will fade light over dark
  Boolean autotoggle = true;  //switch brightmode at pivot point
  float autoPivot = 0.58;  //where on the y axis (0-1) to switch
  int steps[];
  int rw, rh;
  DRIPDRIP() {
    name = "fxDripDrip";
    params.add(new Param ("max steps", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("min steps", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("bright breakpoint", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    //params.add(new Param ("auto toggle", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 3;
    rw = canvas.width;
    rh = canvas.height;
    steps = new int[canvas.width*canvas.height];

    for (int i = 0; i < canvas.width*canvas.height; i++) {
      steps[i] = (int)map(random(1), 0, 1, minsteps, maxsteps);
    }
  }

  void apply() {
    minsteps = (int)map(params.get(1).getValue(), 0, 1, 1, max(canvas.width, canvas.height)/10);
    maxsteps = (int)map(params.get(0).getValue(), 0, 1, minsteps, max(canvas.width, canvas.height)/10);
    autoPivot = params.get(2).getValue();
    //brightmode = boolean((int)params.get(3).getValue());
    //autotoggle = boolean((int)params.get(4).getValue());
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;

      steps = new int[canvas.width*canvas.height];
    }
    for (int i = 0; i < steps.length; i++) {
      steps[i] = (int)map(random(1), 0, 1, minsteps, maxsteps);
    }

    canvas.beginDraw();
    canvas.loadPixels();

    for ( int x = 0, w = canvas.width; x<w; x++) {
      for ( int h = canvas.height, y = h-1; y>-1; y--) {
        /*
           if ( alternatetoggle ) {
         brightmode = !brightmode;
         } else {
         if ( autotoggle ) {
         brightmode = y > (h*autoPivot);
         }
         }
         */
        //if (autotoggle) {
        brightmode = y > (h*autoPivot);
        //}

        float rat = 1.0;
        int pos = x + y * w;
        color c = canvas.pixels[pos];
        int ty = y;

        while ( rat > 1.0/steps[x*y]*2 ) {
          ty++;
          if ( ty >= h ) break;
          int tpos = x + ty * w;
          color tc = canvas.pixels[tpos];
          if (
            ( !brightmode && brightness(tc) < brightness(c) )
            || ( brightmode && brightness(tc) > brightness(c) )
            ) break;
          canvas.pixels[tpos] = blendC(tc, c, rat);
          rat-= rat/steps[x*y];
        }
      }
    }

    canvas.updatePixels();
    canvas.endDraw();
  }
  color blendC(color tc, color sc, float rat) {
    return color(
      (red(tc)*(1.0-rat))+(red(sc)*rat),
      (green(tc)*(1.0-rat))+(green(sc)*rat),
      (blue(tc)*(1.0-rat))+(blue(sc)*rat)
      );
  }
}

/*

 WRONGQSORT
 
 */

class WRONGQSORT extends Shader {
  boolean mode = L; // L or R, which sort part is broken

  boolean do_blend = false; // blend image after process
  int blend_mode = OVERLAY; // blend type

  static final boolean L = true;
  static final boolean R = false;

  // working buffer
  PGraphics buffer;

  // image
  PImage img;

  float random_point = 0.5;
  int len;
  int rw, rh;

  WRONGQSORT() {
    name = "fxWrongQSort";
    params.add(new Param ("v", FLOATVAL, 0.1, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("randomize", FLOATVAL, 0.1, 0.9, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("blend mode", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param ("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 5;

    img = createImage(canvas.width, canvas.height, ARGB);
    img = canvas.get();

    buffer = createGraphics(canvas.width, canvas.height);
    buffer.beginDraw();
    buffer.noStroke();
    buffer.smooth(8);
    buffer.background(0);
    buffer.image(img, 0, 0);
    buffer.endDraw();
  }

  void apply() {
    random_point = params.get(1).getValue();
    mode = boolean((int)params.get(2).getValue());
    do_blend = boolean((int)params.get(3).getValue());
    blend_mode = blends[(int)params.get(4).getValue()];

    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      img.resize(rw, rh);
      buffer = createGraphics(rw, rh);
    }
    img = canvas.get();

    len = rw * rh;
    int v = (int)map(params.get(0).getValue(), 0, 1, 1, len-1);

    buffer.beginDraw();
    buffer.image(img, 0, 0);
    buffer.endDraw();

    buffer.loadPixels();
    int x = 0;
    while (x<len) {
      if (x+v<len) quicksort(buffer.pixels, x, x+v);
      else quicksort(buffer.pixels, x, len-1);
      x+=v;
    }
    buffer.updatePixels();

    buffer.beginDraw();
    if (do_blend) {
      buffer.blend(img, 0, 0, img.width, img.height, 0, 0, buffer.width, buffer.height, blend_mode);
    }
    buffer.endDraw();

    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }


  int partition(int x[], int left, int right) {
    int i = left;
    int j = right;
    int temp;
    int pivot = x [(int)map(random_point, 0, 1, left, right)];
    while (i<= j) {
      while (x[i] < pivot) {
        i++;
      }
      while (x[j] > pivot) {
        j--;
      }
      if (i <= j) {
        temp = x[i];
        x[i] = x [j];
        x[j] = temp;
        i++;
        j--;
      }
    }
    return i;
  }


  void quicksort(int x[], int left, int right) {
    if (left<right) {
      int index = partition(x, left, right);
      if (mode) {
        if (left < index-1) quicksort(x, left, index-1);
        if (right < index) quicksort(x, index, right);
      } else {
        if (left > index-1) quicksort(x, left, index-1);
        if (right > index) quicksort(x, index, right);
      }
    }
  }
}



/*

 VHS
 
 */

class VHS extends Shader {
  PImage timg;
  int bass, treble, sxl, syl;
  VHS () {
    name = "fxVHS";
    params.add(new Param ("sine x length", INTVAL, 1, 100, new int[]{TRIANG, SINE}));
    params.add(new Param ("sine y length", INTVAL, 1, 100, new int[]{TRIANG, SINE}));
    params.add(new Param ("bass", INTVAL, -15, 15, new int[]{TRIANG, SINE}));
    params.add(new Param ("treble", INTVAL, -15, 15, new int[]{TRIANG, SINE}));

    params.add(new Param ("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 4;
  }

  void apply() {
    sxl = (int)params.get(0).getValue();
    syl = (int)params.get(1).getValue();
    bass = (int)params.get(2).getValue();
    treble = (int)params.get(3).getValue();


    int virtw = canvas.width;
    int virth = canvas.height;
    PImage timg = createImage(canvas.width, canvas.height, RGB);
    timg = canvas.get();

    PImage hay = sinwav(timg, sxl, syl);
    PImage result = createImage(timg.width, timg.height, RGB);
    result.copy(hay, 0, 0, virtw, virth, 0, 0, result.width, result.height);

    canvas.beginDraw();
    canvas.image(basstreble(result, bass, treble), canvas.width/2, canvas.height/2, canvas.width, canvas.height); //0 to 100
    canvas.endDraw();
  }

  PImage sinwav(PImage img, int xp, int yp) {
    int ctr = 0;
    img.loadPixels();
    float ylength = xp;
    float xlength = yp; // was map (yp, 0,100, 1, 100);
    PImage result = createImage(img.width, img.height, RGB);
    for ( int y =0, h = img.height; y<h; y++) {
      for ( int x = 0, w = img.width; x<w; x++, ctr++) {
        int pos = x + y * w;
        color c = img.pixels[pos];
        int epos = (int)(( x + sin(y/ylength)*xlength)+ y * w);
        if ( epos < result.pixels.length )
          result.pixels[epos] = c;
        else
          result.pixels[pos] = c;
      }
    }
    result.updatePixels();
    return result;
  }

  PImage basstreble(PImage img, int xp, int yp) {
    float dB_bass = xp;
    float dB_treble = yp;
    PImage result = createImage(img.width, img.height, RGB);
    float slope = 0.4;
    double hzBass = 250.0;
    double hzTreble = 4000.0;
    float b0, b1, b2, a0, a1, a2, xn2Bass, xn1Bass, yn2Bass, yn1Bass, b0Bass, b1Bass, b2Bass, xn1Treble, xn2Treble, yn1Treble, yn2Treble, a0Bass, a1Bass, a2Bass, a0Treble, a1Treble, a2Treble, b0Treble, b1Treble, b2Treble;
    double mMax = 0.0;
    double mSampleRate = 44000;

    xn1Bass = xn2Bass = yn1Bass = yn2Bass = 0.0;
    xn1Treble = xn2Treble = yn1Treble = yn2Treble = 0.0;

    float w = (float)(2 * PI * hzBass / mSampleRate);
    float a = exp((float)(log(10.0) *  dB_bass / 40));
    float b = sqrt((float)((a * a + 1) / slope - (pow((float)(a - 1), 2))));

    b0Bass = a * ((a + 1) - (a - 1) * cos(w) + b * sin(w));
    b1Bass = 2 * a * ((a - 1) - (a + 1) * cos(w));
    b2Bass = a * ((a + 1) - (a - 1) * cos(w) - b * sin(w));
    a0Bass = ((a + 1) + (a - 1) * cos(w) + b * sin(w));
    a1Bass = -2 * ((a - 1) + (a + 1) * cos(w));
    a2Bass = (a + 1) + (a - 1) * cos(w) - b * sin(w);

    w = (float)(2 * PI * hzTreble / mSampleRate);
    a = exp((float)(log(10.0) * dB_treble / 40));
    b = sqrt((float)((a * a + 1) / slope - (pow((float)(a - 1), 2))));



    b0Treble = a * ((a + 1) + (a - 1) * cos(w) + b * sin(w));
    b1Treble = -2 * a * ((a - 1) + (a + 1) * cos(w));
    b2Treble = a * ((a + 1) + (a - 1) * cos(w) - b * sin(w));
    a0Treble = ((a + 1) - (a - 1) * cos(w) + b * sin(w));
    a1Treble = 2 * ((a - 1) - (a + 1) * cos(w));
    a2Treble = (a + 1) - (a - 1) * cos(w) - b * sin(w);


    img.loadPixels();
    result.loadPixels();
    for ( int i = 0, l = img.pixels.length; i<l; i++) {
      int[] rgb = new int[3];
      rgb[0] = (int)red(img.pixels[i]);
      rgb[1] = (int)green(img.pixels[i]);
      rgb[2] = (int)blue(img.pixels[i]);
      for ( int ri = 0; ri<3; ri++ ) {
        float in = map(rgb[ri], 0, 255, 0, 1);

        float out = (b0Bass * in + b1Bass * xn1Bass + b2Bass * xn2Bass - a1Bass * yn1Bass - a2Bass * yn2Bass ) / a0Bass;
        //println(a0Bass);
        xn2Bass = xn1Bass;
        xn1Bass = in;
        yn2Bass = yn1Bass;
        yn1Bass = out;
        //treble filter
        in = out;
        out = (b0Treble * in + b1Treble * xn1Treble + b2Treble * xn2Treble - a1Treble * yn1Treble - a2Treble * yn2Treble) / a0Treble;
        xn2Treble = xn1Treble;
        xn1Treble = in;
        yn2Treble = yn1Treble;
        yn1Treble = out;

        //retain max value for use in normalization
        if ( mMax < abs(out))
          mMax = abs(out);




        rgb[ri] = (int)map(out, 0, 1, 0, 255);
      }
      result.pixels[i] = color(rgb[0], rgb[1], rgb[2]);
    }
    result.updatePixels();

    return result;
  }
}

/*

 LZ7
 
 */

class LZ7 extends Shader {
  // choose colorspace
  int colorspace = HSB; // HSB or RGB
  // set compressor attributes for each channel in chosen colorspace
  //   first number is length of dictionary in LZ77 - values 100 - 4000
  //   second number is length of word (ahead buffer) in LZ77 - about 5% - 50% of dictionary size
  int[][] compressor_attributes = { {2000, 250}, // channel 1 (H or R)
    {50, 10}, // channel 2 (S or G)
    {100, 100} };  // channel 3 (B or B)
  // set number of glitches made for each channel
  //   first number is actual number of random change in compressed channel
  //   second number is amount of change (values from 0.01 to 4)
  float[][] number_of_glitches = { {5000, 2}, // channel 1
    {500, 1}, // channel 2
    {50, 0.1} };   // channel 3

  // working buffer
  PGraphics buffer;

  // image
  PImage img;


  boolean do_blend = false; // blend image after process
  int blend_mode = OVERLAY; // blend type


  LZ77 comp1, comp2, comp3;
  byte[] cr, cb, cg;




  //red channel dictionary size
  //red channel word size
  //red channel randomness
  //red channel amount of change


  LZ7() {
    name = "fxLZ77";
    params.add(new Param("dictionary size (red/hue)", INTVAL, 100, 3000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("word length (red/hue)", FLOATVAL, 0.05, 0.5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("randomness (red/hue)", INTVAL, 20, 5000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("amount of change (red/hue)", FLOATVAL, 0.01, 4, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("dictionary size (green/saturation)", INTVAL, 100, 3000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("word length (green/saturation)", FLOATVAL, 0.05, 0.5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("randomness (green/saturation)", INTVAL, 20, 5000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("amount of change (green/saturation)", FLOATVAL, 0.01, 4, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("dictionary size (blue/brightness)", INTVAL, 100, 3000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("word length (blue/brightness)", FLOATVAL, 0.05, 0.5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("randomness (blue/brightness)", INTVAL, 20, 5000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("amount of change (blue/brightness)", FLOATVAL, 0.01, 4, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("colorspace (rgb / hsb)", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("blend mode", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 15;

    rw = canvas.width;
    rh = canvas.height;

    buffer = createGraphics(rw, rh);
    buffer.beginDraw();
    buffer.noStroke();
    //buffer.smooth(8);
    buffer.background(0);
    buffer.endDraw();
    img = createImage(canvas.width, canvas.height, RGB);

    cr = new byte[rw*rh];
    cb = new byte[rw*rh];
    cg = new byte[rw*rh];
  }

  int rw, rh;
  void apply() {
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      cr = new byte[rw*rh];
      cb = new byte[rw*rh];
      cg = new byte[rw*rh];
      buffer = createGraphics(rw, rh);
      img.resize(rw, rh);
    }
    img = canvas.get();

    colorspace = (int)params.get(12).getValue() == 0 ?RGB:HSB;
    do_blend = boolean((int)params.get(13).getValue());
    blend_mode = blends[(int)params.get(14).getValue()];


    //red
    compressor_attributes[0][0] = (int)params.get(0).getValue();
    compressor_attributes[0][1] = (int)(params.get(1).getValue()*compressor_attributes[0][0]);
    number_of_glitches[0][0] = (int)params.get(2).getValue();
    number_of_glitches[0][1] = params.get(3).getValue();
    //green
    compressor_attributes[1][0] = (int)params.get(4).getValue();
    compressor_attributes[1][1] = (int)(params.get(5).getValue()*compressor_attributes[1][0]);
    number_of_glitches[1][0] = (int)params.get(6).getValue();
    number_of_glitches[1][1] = params.get(7).getValue();
    //blue
    compressor_attributes[2][0] = (int)params.get(8).getValue();
    compressor_attributes[2][1] = (int)(params.get(9).getValue()*compressor_attributes[2][0]);
    number_of_glitches[2][0] = (int)params.get(10).getValue();
    number_of_glitches[2][1] = params.get(11).getValue();

    comp1 = new LZ77( compressor_attributes[0][0], compressor_attributes[0][1] );
    comp2 = new LZ77( compressor_attributes[1][0], compressor_attributes[1][1] );
    comp3 = new LZ77( compressor_attributes[2][0], compressor_attributes[2][1] );

    buffer.beginDraw();

    //print("Preparing... ");

    img.loadPixels();
    int iter=0;
    for (int i=0; i<img.pixels.length; i++) {
      color c = img.pixels[i];
      if (colorspace == HSB) {
        cr[iter]= (byte)hue(c);
        cg[iter]= (byte)saturation(c);
        cb[iter]= (byte)brightness(c);
      } else {
        cr[iter]= (byte)red(c);
        cg[iter]= (byte)green(c);
        cb[iter]= (byte)blue(c);
      }
      iter++;
    }
    //println("done");

    //print("Glitching channel 1... ");
    comp1.doCompress(cr);
    comp1.glitch( (int)number_of_glitches[0][0], number_of_glitches[0][1] );
    comp1.doDecompress(cr);
    //println("done");

    //print("Glitching channel 2... ");
    comp2.doCompress(cg);
    comp2.glitch( (int)number_of_glitches[1][0], number_of_glitches[1][1] );
    comp2.doDecompress(cg);
    //println("done");

    //print("Glitching channel 3... ");
    comp3.doCompress(cb);
    comp3.glitch( (int)number_of_glitches[2][0], number_of_glitches[2][1] );
    comp3.doDecompress(cb);
    //println("done");
    for (int i = 0; i < 3; i++) {
      //println("Channel "+(i+1)+" = ("+compressor_attributes[i][0]+","+compressor_attributes[i][1]+")" + ", glitches = ("+number_of_glitches[i][0]+","+number_of_glitches[i][1]+")");
    }

    buffer.loadPixels();
    if (colorspace == HSB) colorMode(HSB);
    else colorMode(RGB);
    iter=0;
    for (int i=0; i<buffer.pixels.length; i++) {
      float r = cr[iter];
      r = r>=0?r:r+256;
      float g = cg[iter];
      g = g>=0?g:g+256;
      float b = cb[iter];
      b = b>=0?b:b+256;

      buffer.pixels[i] = color(r, g, b);
      iter++;
    }

    buffer.updatePixels();
    if (do_blend)
      buffer.blend(img, 0, 0, img.width, img.height, 0, 0, buffer.width, buffer.height, blend_mode);
    buffer.endDraw();

    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }
}


class Tuple {
  public int offset, len;
  byte chr;
  public Tuple(int o, int l, byte c) {
    offset = o;
    len = l;
    chr = c;
  }
}

class LZ77 {
  int windowWidth;
  int lookAheadWidht;

  public LZ77(int ww, int law) {
    windowWidth = ww;
    lookAheadWidht = law;
  }

  ArrayList<Tuple> clist = new ArrayList<Tuple>();

  public void glitch(int no, float fac) {
    for (int i=0; i<no; i++) {
      Tuple r = clist.get( (int)random(clist.size()));
      int what = (int)random(3);
      switch(what) {
      case 0:
        r.chr = (byte)random(256);
        break;
      case 1:
        r.offset = (int)random(2*windowWidth*fac);
        break;
      default:
        r.len = (int)random(2*lookAheadWidht*fac);
      }
    }
  }

  public void doCompress(byte[] buff) {
    int currByte = 1;

    // first is always byte
    clist.add( new Tuple(0, 0, buff[0]) );

    while (currByte < buff.length) {
      int bend = constrain(currByte-windowWidth, 0, buff.length);
      int boff = 0;
      int blen = 0;

      if (currByte<buff.length-1)
        for (int i = currByte-1; i>=bend; i--) {
          if (buff[currByte] == buff[i]) {

            int tboff = abs(i-currByte);
            int tblen = 1;
            int laEnd = constrain(currByte+lookAheadWidht, 0, buff.length-1);
            int mi = currByte+1;

            while (mi<laEnd && (i+mi-currByte)<currByte) {
              if (buff[mi] == buff[i+mi-currByte]) {
                mi++;
                tblen++;
              } else {
                break;
              }
            }

            if (tblen>blen) {
              blen = tblen;
              boff = tboff;
            }
          }
        }

      currByte +=blen+1;
      // println("currchar = " + (currByte-1)+",blen = " + blen);
      clist.add( new Tuple(boff, blen, buff[currByte-1]) );
      // println(boff + ", " + blen + ", " + buff[currByte-1]);
    }
    //println("clist " + clist.size()*2);
  }

  void doDecompress(byte[] buff) {
    int i = 0;
    for (Tuple t : clist) {
      if (i>=buff.length) break;
      if (t.offset == 0) {
        buff[i++] = t.chr;
      } else {
        int start = i-t.offset;
        int end = start + t.len;
        for (int c = start; c<end; c++) {
          int pos = constrain(c, 0, buff.length-1);
          buff[constrain(i++, 0, buff.length-1)] = buff[pos];
          if (i>=buff.length) break;
        }
        if (i>=buff.length) break;
        buff[i++] = t.chr;
      }
    }
  }
}

/*

 LENS
 
 */

class LENS extends Shader {

  // parameters
  float bendx = 0.1; // from 0 to 1
  float bendy = 0.1; // from 0 to 1
  float[] power_vals = { 2, 0.5 }; // two values of power from 0.1 to 10, one for x and second for y
  int[] types = { LINEAR, POWER }; // always to types one for x second for y
  int[] channels = { BRIGHTNESS, SATURATION }; // as above
  float[] facts = new float[2];
  //////////////
  PImage img;
  PImage limg;

  int shaderListLength;
  // working buffer
  PGraphics buffer;

  LENS() {
    name = "fxLens";
    shaderListLength = gui.shaderList.size();
    params.add(new Param("lens image layer", INTVAL, 0, shaderListLength-1, new int[]{RANDOM}));
    params.add(new Param("power x", FLOATVAL, 0.1, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("power y", FLOATVAL, 0.1, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("lens type x", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("lens type y", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("channel x", INTVAL, 0, 11, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("channel y", INTVAL, 0, 11, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("curvature factor x", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("curvature factor y", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 9;

    rw = canvas.width;
    rh = canvas.height;

    img = createImage(rw, rh, ARGB);
    limg = createImage(rw, rh, ARGB);

    buffer = createGraphics(rw, rh);
    buffer.beginDraw();
    buffer.noStroke();
    buffer.endDraw();
  }

  int rw, rh;
  void apply() {
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      buffer = createGraphics(rw, rh);
      buffer.beginDraw();
      buffer.noStroke();
      buffer.endDraw();
      img.resize(rw, rh);
      limg.resize(rw, rh);
    }
    if (shaderListLength != gui.shaderList.size()) {
      shaderListLength = gui.shaderList.size();
      changeParam(0, new Param("lens image layer", INTVAL, 0, shaderListLength-1, new int[]{RANDOM}));
    }
    img = canvas.get();
    if ((int)params.get(0).getValue() == pos) {
      limg = canvas.get();
    } else {
      limg = gui.shaderList.get((int)params.get(0).getValue()).result.get();
    }

    power_vals[0] = params.get(1).getValue();
    power_vals[1] = params.get(2).getValue();
    types[0] = (int)params.get(3).getValue();
    types[1] = (int)params.get(4).getValue();
    channels[0] = (int)params.get(5).getValue();
    channels[1] = (int)params.get(6).getValue();
    bendx = params.get(7).getValue();
    bendy = params.get(8).getValue();
    facts[0] = bendx * img.width;
    facts[1] = bendy * img.height;


    img.loadPixels();
    limg.loadPixels();
    buffer.beginDraw();
    buffer.background(0);

    for (int x=0; x<img.width; x++) {

      int lx = (int)map(x, 0, img.width-1, 0, limg.width-1);

      for (int y=0; y<img.height; y++) {

        int ly = (int)map(y, 0, img.height-1, 0, limg.height-1);

        color c = limg.pixels[lx+ly*limg.width];

        int posx = (x+getShift(c, 0)+2*img.width)%img.width;
        int posy = (y+getShift(c, 1)+2*img.height)%img.height;

        color n = img.pixels[posx+posy*img.width];

        buffer.fill(n);
        //  fill(red(c),green(c),blue(n)); // work only on blue channel
        //  fill(red(n), abs(green(c)-green(n)), blue(n)); // green channel is blended using difference method
        buffer.rect(x, y, 1, 1);
      }
    }

    buffer.endDraw();
    img.updatePixels();
    limg.updatePixels();
    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }


  float getChannel(color c, int channel) {
    int ch = channel>5?channel-6:channel;
    float cc;

    switch(ch) {
    case RED:
      cc = red(c);
      break;
    case GREEN:
      cc = green(c);
      break;
    case BLUE:
      cc = blue(c);
      break;
    case HUE:
      cc = hue(c);
      break;
    case SATURATION:
      cc = saturation(c);
      break;
    default:
      cc= brightness(c);
      break;
    }

    return channel>5?255-cc:cc;
  }

  int getShift(color c, int idx) {
    float cc = getChannel(c, channels[idx]);

    switch(types[idx]) {
    case LINEAR:
      return (int)(facts[idx] * cc/255.0);
    case POWER:
      return (int)(facts[idx]*map(pow(cc/255.0, power_vals[idx]), 0, 1, -1, 1));
    case SINUSOIDAL:
      return (int)(facts[idx]*sin(map(cc, 0, 255, -PI, PI)));
    default:
      { // POLAR
        float c1 = idx==0?cc:getChannel(c, channels[1]);
        float c2 = idx==1?cc:getChannel(c, channels[0]);
        float ang = map(c1, 0, 255, 0, TWO_PI);
        float r = map(c2, 0, 255, 0, facts[0]);
        return (int)(idx==0?r*cos(ang):r*sin(ang));
      }
    }
  }
}


/*

 SLICER
 
 */


class SLICER extends Shader {


  ArrayList<Edge> edges = new ArrayList<Edge>();
  SegmentNode[] elements;
  Map<Integer, S> m = new HashMap<Integer, S>();
  int max_patterns = 20; // CAN I KILL THIS?

  float max_rotation = TWO_PI; // random or fixed rotation up to specified angle, 0 - TWO_PI, 0 - no rotation
  boolean fixed_rotation = true; // fixed or random rotation
  int thr_min = -1; // you can change color of segment for specified brightness threshold
  int thr_max = -1; // set to -1 if you don't want to use it, values from 0 to 255
  color[] palette = { #000000, #E7CBB3, #CEC6AF, #A0C3BC, #7C7F84, #ffffff }; // choose colors for palette you want to use with threshold, colors will be choosen randomly


  // SEPARATE config
  float max_shift = 2; // max expected shift, using gaussian random, so bigger number, more distortion

  int type = ROTATE; // choose type of distortion: PATTERNS, SEPARATE, ROTATE, SHIFTCOPY, SORT
  boolean do_blend = false; // blend result with original?
  int blend_type = SUBTRACT; // list here: https://processing.org/reference/blend_.html

  // segmentation config START
  float threshold = 500.0; // higher number - bigger segments
  int min_comp_size = 200; // minimal segment size (in pixels), minimum 10
  int blur = 0; // sometimes it's good to blur image to have less sharp segment edges, 0 = off, blur > 0 - blur kernel size
  int stat_type = DIST; // edge calculation method

  final static int DIST = 0;
  final static int HUE = 1;
  final static int BRIGHTNESS = 2;
  final static int SATURATION = 3;
  final static int ABSDIST = 4;

  // do not touch it
  PImage img, mimg; // load image to this variable
  int num;
  // segmentation config END

  // do not touch, list of types
  final static int PATTERNS = 0; // fill segments with patterns
  final static int SEPARATE = 1; // separate segments (black background visible)
  final static int ROTATE = 2; // rotate content of the segments
  final static int SHIFTCOPY = 3; // copy content from shifted image
  final static int SORT = 4; // sort segments by color value

  // working buffer
  PGraphics buffer;
  SLICER() {
    name = "fxSlicer";
    params.add(new Param("mask layer", INTVAL, 0, shaderListLength-1, new int[]{RANDOM}));
    params.add(new Param("max rotation", FLOATVAL, 0, TWO_PI, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("min threshold", INTVAL, -1, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("max threshold", INTVAL, -1, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("max shift", FLOATVAL, 0, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("type", INTVAL, 0, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("blend type", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param("threshold", FLOATVAL, 100, 2000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("min comp size", INTVAL, 10, 500, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("blur", INTVAL, 0, 6, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("stat type", INTVAL, 0, 6, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("fixed rotation", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 13;

    shaderListLength = gui.shaderList.size();

    img = createImage(canvas.width, canvas.height, ARGB);
    mimg = createImage(canvas.width, canvas.height, ARGB);

    buffer = createGraphics(img.width, img.height);
    buffer.beginDraw();
    buffer.noStroke();
    buffer.smooth(8);
    buffer.background(0);
    buffer.endDraw();
  }

  int rw, rh, shaderListLength;
  void apply() {
    if (canvas.width != rw || canvas.height != rh) {
      rw = canvas.width;
      rh = canvas.height;
      buffer = createGraphics(rw, rh);
      buffer.beginDraw();
      buffer.noStroke();
      buffer.endDraw();

      img.resize(rw, rh);
      mimg.resize(rw, rh);
    }

    if (shaderListLength != gui.shaderList.size()) {
      shaderListLength = gui.shaderList.size();
      changeParam(0, new Param("mask layer", INTVAL, 0, shaderListLength-1, new int[]{RANDOM}));
    }
    img = canvas.get();
    if ((int)params.get(0).getValue() == pos) {
      mimg = canvas.get();
    } else {
      mimg = gui.shaderList.get((int)params.get(0).getValue()).result.get();
    }


    max_rotation = params.get(1).getValue();
    thr_min = (int)params.get(2).getValue();
    thr_max = (int)params.get(3).getValue();
    max_shift = params.get(4).getValue();
    type = (int)params.get(5).getValue();
    do_blend = boolean((int)params.get(6).getValue());
    blend_type = blends[(int)params.get(7).getValue()];
    threshold = params.get(8).getValue();
    min_comp_size = (int)params.get(9).getValue();
    blur = (int)params.get(10).getValue();
    stat_type = (int)params.get(11).getValue();
    fixed_rotation = boolean((int)params.get(12).getValue());

    //println("");
    //println("Processing...");
    edges.clear();
    makeEdges();
    calculateSegmentation();


    buffer.beginDraw();
    m.clear();

    if (type == PATTERNS) preparePatterns();

    for (int x=0; x<img.width; x++)
      for (int y=0; y<img.height; y++) {
        Integer segment = findEnd(y*img.width+x);

        S segm;
        if (m.containsKey(segment)) {
          segm = m.get(segment);
          if (type == SORT) sortHelper(segm, x, y);
        } else {
          segm = new S();
          segm.rots = sin(fixed_rotation?max_rotation:random(max_rotation));
          segm.rotc = cos(fixed_rotation?max_rotation:random(max_rotation));
          segm.c = img.get(x, y);
          segm.dx = (int)(max_shift*randomGaussian());
          segm.dy = (int)(max_shift*randomGaussian());
          segm.x = x;
          segm.y = y;
          if (brightness(segm.c)>=thr_min && brightness(segm.c)<=thr_max) segm.c = palette[(int)random(palette.length)];
          if (type == PATTERNS) {
            segm.pat = patterns[(int)random(max_patterns)];
            segm.pats = random(0.5, 10);
          }
          if (type == SORT) {
            segm.xy = random(1)<0.5;
            int size = elements[segment].size;
            segm.clrs = new color[size];
            segm.positions = new int[size];
            segm.iters = 0;
            sortHelper(segm, x, y);
          }
          m.put(segment, segm);
        }

        if (type == PATTERNS) {
          int vx = segm.x-x;
          int vy = segm.y-y;
          float sinr = segm.rots;
          float cosr = segm.rotc;
          int imgx = int(segm.pat.width+x+(cosr*vx-sinr*vy))%segm.pat.width;
          int imgy = int(segm.pat.height+x+(sinr*vx+cosr*vy))%segm.pat.height;
          buffer.fill(segm.pat.get(imgx, imgy));
          buffer.rect(x, y, 1, 1);
        } else if (type == SEPARATE) {
          buffer.fill(segm.c);
          buffer.rect(x+segm.dx, y+segm.dy, 1, 1);
        } else if (type == SHIFTCOPY) {
          int imgx = (2*x-segm.x)%img.width;
          int imgy = (2*y-segm.y)%img.height;
          buffer.fill(img.get(imgx, imgy));
          buffer.rect(x, y, 1, 1);
        } else if (type == ROTATE) {
          int vx = segm.x-x;
          int vy = segm.y-y;
          float sinr = segm.rots;
          float cosr = segm.rotc;
          int imgx = int(img.width+x+(cosr*vx-sinr*vy))%img.width;
          int imgy = int(img.height+y+(sinr*vx+cosr*vy))%img.height;
          buffer.fill(img.get(imgx, imgy));
          buffer.rect(x, y, 1, 1);
        }
      }

    if (type == SORT) {
      for (Integer key : m.keySet()) {
        S segm = m.get(key);
        segm.clrs = sort(segm.clrs);
        segm.positions = sort(segm.positions);
      }

      for (Integer key : m.keySet()) {
        S segm = m.get(key);
        for (int i=0; i<segm.positions.length; i++) {
          int x, y;
          if (segm.xy) {
            x = (segm.positions[i] >> 16) & 0xffff;
            y = segm.positions[i] & 0xffff;
          } else {
            y = (segm.positions[i] >> 16) & 0xffff;
            x = segm.positions[i] & 0xffff;
          }
          buffer.fill(segm.clrs[i]);
          buffer.rect(x, y, 1, 1);
        }
      }
    }

    if (do_blend)
      buffer.blend(img, 0, 0, img.width, img.height, 0, 0, buffer.width, buffer.height, blend_type);

    buffer.endDraw();
    canvas.beginDraw();
    canvas.image(buffer, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }


  void sortHelper(S segm, int x, int y) {
    int xyval;
    if (segm.xy)
      xyval = (x << 16) | y;
    else
      xyval = (y << 16) | x;
    segm.clrs[segm.iters] = img.get(x, y);
    segm.positions[segm.iters++] = xyval;
  }

  final float getStat(color c1, color c2) {
    switch(stat_type) {
    case HUE:
      abs(hue(c1)-hue(c2));
    case BRIGHTNESS:
      abs(brightness(c1)-brightness(c2));
    case SATURATION:
      abs(saturation(c1)-saturation(c2));
    case ABSDIST:
      return abs(red(c1)-red(c2)) + abs(green(c1)-green(c2)) + abs(blue(c1)-blue(c2));
    default:
      return sq(red(c1)-red(c2)) + sq(green(c1)-green(c2)) + sq(blue(c1)-blue(c2));
    }
  }
  void makeEdges() {
    PImage img2 = mimg.get(0, 0, img.width, img.height);
    if (blur > 0) img2.filter(BLUR, blur);
    // make grid each point connected to neighbours
    for (int x=0; x<img2.width; x++)
      for (int y=0; y<img2.height; y++) {
        color c = img2.get(x, y);

        if (x<img2.width-1) {
          Edge e = new Edge();
          e.a = y*img2.width+x;
          e.b = y*img2.width+x+1;
          e.weight = getStat(c, img2.get(x+1, y));
          edges.add(e);
        }

        if (y<img2.height-1) {
          Edge e = new Edge();
          e.a = y*img2.width+x;
          e.b = (y+1)*img2.width+x;
          e.weight = getStat(c, img2.get(x, y+1));
          edges.add(e);
        }

        if ( (x<img2.width-1) && (y<img2.height-1)) {
          Edge e = new Edge();
          e.a = y*img2.width+x;
          e.b = (y+1)*img2.width+x+1;
          e.weight = getStat(c, img2.get(x+1, y+1));
          edges.add(e);
        }

        if ( (x<img2.width-1) && (y>0)) {
          Edge e = new Edge();
          e.a = y*img2.width+x;
          e.b = (y-1)*img2.width+x+1;
          e.weight = getStat(c, img2.get(x+1, y-1));
          edges.add(e);
        }
      }

    // sort edges
    Collections.sort(edges);
  }

  int findEnd(int x) {
    int y = x;
    while (y != elements[y].parent) y = elements[y].parent;
    elements[x].parent = y;
    return y;
  }

  void joinSegments(int x, int y) {
    if (elements[x].rank > elements[y].rank) {
      elements[y].parent = x;
      elements[x].size += elements[y].size;
    } else {
      elements[x].parent = y;
      elements[y].size += elements[x].size;
      if (elements[x].rank == elements[y].rank) elements[y].rank++;
    }
    num--;
  }


  void calculateSegmentation() {
    int no_vertices = img.width*img.height;
    num = no_vertices;

    elements = new SegmentNode[no_vertices];
    // init nodes
    for (int i=0; i<no_vertices; i++) {
      SegmentNode s = new SegmentNode();
      s.rank = 0;
      s.size = 1;
      s.parent = i;
      elements[i]=s;
    }

    float[] thresholds = new float[no_vertices];
    Arrays.fill(thresholds, threshold);

    for (Edge edge : edges) {
      int a = findEnd(edge.a);
      int b = findEnd(edge.b);

      if (a!=b) {
        if (edge.weight <= thresholds[a] && edge.weight <= thresholds[b]) {
          joinSegments(a, b);
          a = findEnd(a);
          thresholds[a] = edge.weight + threshold/elements[a].size;
        }
      }
    }

    for (Edge edge : edges) {
      int a = findEnd(edge.a);
      int b = findEnd(edge.b);
      if ( (a != b) && ((elements[a].size < min_comp_size) || (elements[b].size < min_comp_size))) {
        joinSegments(a, b);
      }
    }

    //println("Segments: " +num);
  }


  PImage[] patterns;
  void preparePatterns() {
    patterns = new PImage[max_patterns];
    for (int i=0; i<max_patterns; i++) {
      patterns[i] = getSelfPattern();
    }
  }

  PImage getSelfPattern() {
    int x = (int)random(img.width-50);
    int y = (int)random(img.height-50);
    int sx = (int)random(50, img.width-x-1);
    int sy = (int)random(50, img.height-y-1);
    return img.get(x, y, sx, sy);
  }

  PImage getLayerPattern(int layer) { //use me
    int x = (int)random(gui.shaderList.get(layer).canvas.width-50);
    int y = (int)random(gui.shaderList.get(layer).canvas.height-50);
    int sx = (int)random(50, gui.shaderList.get(layer).canvas.width-x-1);
    int sy = (int)random(50, gui.shaderList.get(layer).canvas.height-y-1);
    return gui.shaderList.get(layer).canvas.get(x, y, sx, sy);
  }
}
// general class to save distortions for particular segment
class S {
  color c; // color of the segment root point
  int x, y; // position of segment root point
  int dx, dy; // segment shift
  float rots, rotc, pats;
  PImage pat;
  // sort
  color[] clrs;
  int[] positions;
  boolean xy;
  int iters;
}
class Edge implements Comparable {
  int a, b;
  float weight;

  int compareTo(Object o) {
    Edge e = (Edge)o;
    return this.weight<e.weight?-1:this.weight>e.weight?1:0;
  }
}
class SegmentNode {
  int rank, parent, size;
}

/*

 STREAKER
 
 */

class STREAKER extends Shader {


  final int EXCLUSIVE = 0;
  final int LESSER = 1;
  final int GREATER = 2;

  final int CLASSIC = 0;
  final int RGBSUM = 1;
  final int BRIGHT = 2;

  PImage img;
  float tol = 100.0; // tolerance
  int mode = LESSER; // EXCLUSIVE, LESSER, GREATER
  int sum = BRIGHT; // CLASSIC, RGBSUM, BRIGHT
  int lim = 1; // move divisor
  int iterations = 1;
  float rand = 5;
  boolean h = false;
  boolean v = true;
  boolean Hrev = false;
  boolean Vrev = false;
  int W, H;

  STREAKER() {
    name = "fxStreaker";
    img = createImage(canvas.width, canvas.height, ARGB);
    params.add(new Param("vertical/horizontal/both", INTVAL, 0, 2, new int[]{RANDOM}));
    params.add(new Param("verticals up / down", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("horicontals up / down", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("tolerance", FLOATVAL, 0, 100, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("tolerance randomness", FLOATVAL, 0, 50, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("detection mode (EXCLUSIVE, LESSER, GREATER)", INTVAL, 0, 2, new int[]{RANDOM}));
    params.add(new Param("sum (RBG sum, brightness, R>G>B)", INTVAL, 0, 2, new int[]{RANDOM}));
    params.add(new Param("move divisor", INTVAL, 1, 5, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("iterations", INTVAL, 1, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 9;

    W = canvas.width;
    H = canvas.height;
  }

  void apply() {
    if (W != canvas.width || H != canvas.height) {
      W = canvas.width;
      H = canvas.height;
      img.resize(W, H);
    }
    img = canvas.get();
    int p0 = (int)params.get(0).getValue();
    v = (p0 == 0 || p0 == 2) ? true : false;
    h = (p0 == 1 || p0 == 2) ? true : false;
    Vrev = boolean((int)params.get(1).getValue());
    Hrev = boolean((int)params.get(2).getValue());
    tol = params.get(3).getValue();
    rand = params.get(4).getValue();
    mode = (int)params.get(5).getValue();
    sum = (int)params.get(6).getValue();
    lim = (int)params.get(7).getValue();
    iterations = (int)params.get(8).getValue();


    PGraphics temp = createGraphics(img.width, img.height);
    int off = 0;
    color c;
    boolean a = false;


    for (int i = 0; i < iterations; i++) {
      img.loadPixels();
      temp.beginDraw();
      temp.image(img, 0, 0);
      if (h) {
        if (!Hrev) {
          for (int y = 0; y < img.height-1; y++) {
            for (int x = 0; x < img.width-1; x++) {
              c = img.pixels[x+(y*img.width)];
              while (check (x, y, x+off+1, y) == true && x+off+1 < img.width) {
                off++;
                a = true;
              }
              if (a) {
                temp.stroke(c);
                temp.line(x, y, x+(off/lim), y);
                x = x+(off/lim);
                a = false;
                off = 0;
              }
            }
          }
        } else { // hrev
          for (int y = 0; y < img.height-1; y++) {
            for (int x = img.width-1; x > 1; x--) {
              c = img.pixels[x+(y*img.width)];
              while (check (x, y, x-off-1, y) == true && x-off-1 > 0) {
                off++;
                a = true;
              }
              if (a) {
                temp.stroke(c);
                temp.line(x, y, x-(off/lim), y);
                x = x-(off/lim);
                a = false;
                off = 0;
              }
            }
          }
        }
      }

      if (v) {
        if (!Vrev) {
          for (int x = 0; x < img.width-1; x++) {
            for (int y = 0; y < img.height-1; y++) {
              c = img.pixels[x+(y*img.width)];
              while (check (x, y, x, y+off+1) == true && y+off+1 < img.height-1 ) {
                off++;
                a = true;
              }
              if (a) {
                temp.stroke(c);
                temp.line(x, y, x, y+(off/lim));
                y = y+(off/lim);
                a = false;
                off = 0;
              }
            }
          }
        } else { //Vrev
          for (int x = 0; x < img.width-1; x++) {
            for (int y = img.height-1; y > 1; y--) {
              c = img.pixels[x+(y*img.width)];
              while (check (x, y, x, y-off-1) == true && y-off-1 > 0) {
                off++;
                a = true;
              }
              if (a) {
                temp.stroke(c);
                temp.line(x, y, x, y-(off/lim));
                y = y-(off/lim);
                a = false;
                off = 0;
              }
            }
          }
        }
      }

      temp.endDraw();
      img = temp.get();
      img.updatePixels();
    }
    canvas.beginDraw();
    canvas.image(img, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }


  boolean check(int x, int y, int xc, int yc) {
    float src, dst, r;
    r = (rand != 0) ? random(0, rand) : 0;
    switch(sum) {
    case 1: //rgbsum
      src = rgbsum(x, y);
      dst = rgbsum(xc, yc);
      break;
    case 2: // brightness
      src = brightness(img.pixels[x+(y*img.width)]);
      dst = brightness(img.pixels[xc+(yc*img.width)]);
      break;
    default: // 'classic' or asdfish
      src = map(img.pixels[x+(y*img.width)], 0, 0xFFFFFF, 0.0, 255.0);
      dst = map(img.pixels[xc+(yc*img.width)], 0, 0xFFFFFF, 0.0, 255.0);
      break;
    }

    if (mode == 0 && (src < dst-tol-r || src > dst+tol+r)) return true;
    else if (mode == 1 && src > dst+tol-r) return true;
    else if (mode == 2 && src < dst-tol+r) return true;
    else return false;
  }

  float rgbsum(int x, int y) {
    int r = img.pixels[x+(y*img.width)] >> 24 & 0xff;
    int g = img.pixels[x+(y*img.width)] >> 16 & 0xff;
    int b = img.pixels[x+(y*img.width)] & 0xff;
    float result = (r + g + b) / 3;
    return result;
  }
}


/*

 SEGMENTER
 
 */

class SEGMENTER extends Shader {
  int thres = 64; //            threshold
  int mode = 0; //              lighter/darker, essentially 'reverses' the sort
  boolean diag = true; //       diagonal/straight
  boolean v = true; //          use to select initial direction.
  boolean h = true; //          if cycling, use V=true H=false.
  boolean choice = false;
  int iterations;
  PImage img;
  int[] bounds = {0, 0, 0, 0};
  boolean running = true;
  SEGMENTER() {
    name = "fxSegmenter";
    params.add(new Param("threshold", INTVAL, 8, 252, new int[]{SINE, TRIANG}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("diagonal", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("horizontal", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("vertical", INTVAL, 0, 1, new int[] {RANDOM, SQUAR}));
    params.add(new Param("weirdo pattern mode", INTVAL, 0, 1, new int[] {RANDOM, SQUAR}));
    params.add(new Param("iterations", INTVAL, 1, 100, new int[] {SINE, TRIANG}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 7;


    img = createImage(canvas.width, canvas.height, ARGB);
    canvas.beginDraw();
    canvas.colorMode(HSB);
    canvas.endDraw();
  }
  int rw, rh;
  void apply() {
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      img.resize(rw, rh);
      bounds[0]= 0;
      bounds[1]= 0;
      bounds[2]= rw;
      bounds[3]= rh;
    }
    img = canvas.get();
    thres = (int)params.get(0).getValue();
    mode = (int)params.get(1).getValue();
    diag = boolean((int)params.get(2).getValue());
    v = boolean((int)params.get(3).getValue());
    h = boolean((int)params.get(4).getValue());
    choice = boolean((int)params.get(5).getValue());
    iterations = (int)params.get(6).getValue();
    canvas.beginDraw();
    canvas.image(img, canvas.width/2, canvas.height/2);
    canvas.endDraw();
    canvas.beginDraw();

    for (int i = 0; i < iterations; i++) {
      canvas.loadPixels();
      for (int j = bounds[1]; j < bounds[3]-1; j++) {
        for (int k = bounds[0]; k < bounds[2]-1; k++) {
          float bright = canvas.brightness(canvas.get(k, j));
          color c;
          if (!choice) {
            if (diag) {
              if (mode == 0) {
                if (h && bright < abs(thres)&& k > bounds[0]+1 && j > bounds[1]+1 && (bright < canvas.brightness(canvas.pixels[k-1+((j-1)*canvas.width)]))) {
                  swap(k, j, k-1, j-1);
                } else if (v && bright < abs(thres*2) && j > bounds[1]+1 && k < bounds[2]-1 && (bright < canvas.brightness(canvas.pixels[k+1+((j-1)*canvas.width)]))) {
                  swap(k, j, k+1, j-1);
                } else if (h && bright < abs(thres*3) && k < bounds[2]-1 && j < bounds[3]-1 && (bright < canvas.brightness(canvas.pixels[k+1+((j+1)*canvas.width)]))) {
                  swap(k, j, k+1, j+1);
                } else if (v && k > bounds[0] && j < bounds[3]-1 && (bright < canvas.brightness(canvas.pixels[k-1+((j+1)*canvas.width)]))) {
                  swap(k, j, k-1, j+1);
                }
              } else { // mode
                if (h && bright < abs(thres)&& k > bounds[0] && j > bounds[1] && (bright > canvas.brightness(canvas.pixels[k-1+((j-1)*canvas.width)]))) {
                  swap(k, j, k-1, j-1);
                } else if (v && bright < abs(thres*2) && j > bounds[1] && k < bounds[2]-1 && (bright > canvas.brightness(canvas.pixels[k+1+((j-1)*canvas.width)]))) {
                  swap(k, j, k+1, j-1);
                } else if (h && bright < abs(thres*3) && k < bounds[2]-1 && j < bounds[3]-1 && (bright > canvas.brightness(canvas.pixels[k+1+((j+1)*canvas.width)]))) {
                  swap(k, j, k+1, j+1);
                } else if (v && k > bounds[0] && j < bounds[3]-1 && (bright > canvas.brightness(canvas.pixels[k-1+((j+1)*canvas.width)]))) {
                  swap(k, j, k-1, j+1);
                }
              } // mode
            }//diag

            else {
              if (mode == 0) {
                if (h && bright < abs(thres)&& k > bounds[0] && (bright < canvas.brightness(canvas.get(k-1, j)))) {
                  swap(k, j, k-1, j);
                } else if (v && bright < abs(thres*2) && j > bounds[1] && (bright < canvas.brightness(canvas.get(k, j-1)))) {
                  swap(k, j, k, j-1);
                } else if (h && bright < abs(thres*3) && k < bounds[2]-1 && (bright < canvas.brightness(canvas.get(k+1, j)))) {
                  swap(k, j, k+1, j);
                } else if (v && k > bounds[0] && j < bounds[3]-1 && (bright < canvas.brightness(canvas.get(k, j+1)))) {
                  swap(k, j, k, j+1);
                }
              } else { // mode
                if (h && bright < abs(thres)&& k > bounds[0] && (bright > canvas.brightness(canvas.get(k-1, j)))) {
                  swap(k, j, k-1, j);
                } else if (v && bright < abs(thres*2) && j > bounds[1] && (bright > canvas.brightness(canvas.get(k, j-1)))) {
                  swap(k, j, k, j-1);
                } else if (h && bright < abs(thres*3) && k < bounds[2]-1 && (bright > canvas.brightness(canvas.get(k+1, j)))) {
                  swap(k, j, k+1, j);
                } else if (v && k > bounds[0] && j < bounds[3]-1 && (bright > canvas.brightness(canvas.get(k, j+1)))) {
                  swap(k, j, k, j+1);
                }
              } // mode
            } // diag
          } else { //choice
            //weirdo pattern mode
            if (mode == 0) {
              if (bright < thres && k-1+((j-1)*canvas.width) > 0) {
                swap(k, j, k-1, j-1);
              } else if (bright < abs(thres*2) && k+1+((j-1)*canvas.width) > 0) {
                swap(k, j, k+1, j-1);
              } else if (bright < abs(thres*3) && k+1+((j+1)*canvas.width) < canvas.width*canvas.height) {
                swap(k, j, k+1, j+1);
              } else if (k-1+((j+1)*canvas.width) < canvas.width*canvas.height) {
                swap(k, j, k-1, j+1);
              }
            } else { // mode
              if (bright > abs(thres*3) && k-1+((j-1)*canvas.width) > 0) {
                if (k-1+((j-1)*canvas.width) > 0) {
                  swap(k, j, k-1, j-1);
                }
              } else
                if (bright > abs(thres*2) && k+1+((j-1)*canvas.width) > 0) {
                  if (k+1+((j-1)*canvas.width) > 0) {
                    swap(k, j, k+1, j-1);
                  }
                } else
                  if (bright < thres  && k+1+((j+1)*canvas.width) < canvas.width*canvas.height) {
                    swap(k, j, k+1, j+1);
                  } else if (k-1+((j+1)*canvas.width) < canvas.width*canvas.height) {
                    swap(k, j, k-1, j+1);
                  }
            } // mode
          }//choice
        }//hori loop
      }//vert loop
      canvas.updatePixels();
    }//iterations
    canvas.endDraw();
  }

  void swap(int x, int y, int xc, int yc) {
    //  if (x+(y*width) < width*height && xc+(yc*width) < width*height && xc+(yc*width) > -1) {
    color c = canvas.pixels[x+(y*canvas.width)];
    canvas.pixels[x+(y*canvas.width)] =  canvas.pixels[xc+(yc*canvas.width)];
    canvas.pixels[xc+((yc)*canvas.width)] = c;
    //  }
  }
}

/*

 COLORCRUSHER
 
 */

class COLORCRUSHER extends Shader {
  float c;
  int m = 5;
  COLORCRUSHER() {
    name = "fxColorCrusher";
    params.add(new Param("multiplier", INTVAL, 2, 10, new int[]{SINE, TAN, SAWTOOTH, TRIANG}));
  }
  void apply() {
    m = (int)params.get(0).getValue();
    canvas.beginDraw();
    canvas.loadPixels();
    for (int i = 0; i < canvas.pixels.length; i++) {
      c = color(canvas.pixels[i]);
      canvas.pixels[i] = int(c*m);
    }
    canvas.updatePixels();
    canvas.endDraw();
  }
}

/*

 RAINBOWHUE
 
 */

//...

/*

 JPGCorruption
 by Victor Giers
 
 */

class JPGCORRUPTION extends Shader {
  PImage img;
  byte[] brokenfile;
  JPGCORRUPTION() {
    name = "fxJPGHexGlitch";
    params.add(new Param("byte amount to change probability", INTVAL, 2, 200, new int[]{RANDOM}));
    params.add(new Param("iterations", INTVAL, 2, 10, new int[]{RANDOM}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 2;
    rw = canvas.width;
    rh = canvas.height;
    img = createImage(rw, rh, ARGB);
  }
  int rw, rh;
  void apply() {
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      img.resize(rw, rh);
    }
    int probparam = (int)params.get(0).getValue();
    int iterations = (int)params.get(1).getValue();
    img = canvas.get();
    canvas.beginDraw();
    for (int k = 0; k < iterations; k++) {
      canvas.image(img, canvas.width/2, canvas.height/2);
      canvas.save(dataPath("")+"/JPGHex.jpg"); //save as jpg
      brokenfile = loadBytes(dataPath("")+"/JPGHex.jpg"); //and reload. just in case it wasnt a jpg.
      double probability = float(probparam) / float(brokenfile.length);
      byte[] savebytes2 = new byte[brokenfile.length];
      boolean headerEnd = false;
      boolean FFfound = false;
      int glitchCount = 0;

      for (int i = 0; i < brokenfile.length; i++) {
        String hexStr = hex(brokenfile[i]);

        if (FFfound && hexStr.equals("DA")) headerEnd = true;
        FFfound = false;
        if (hexStr.equals("FF")) FFfound = true;

        if (headerEnd && random(1)<probability) {
          String randomHex = (str(Character.forDigit((int)random(16), 16)) + str(Character.forDigit((int)random(16), 16))).toUpperCase();
          hexStr = randomHex;
          glitchCount++;
        }
        byte hexByte[] = fromHexString(hexStr);
        for (int j = 0; j < hexByte.length; j++) {
          savebytes2[i] += hexByte[j];
        }
      }
      //println("Glitched " + glitchCount + " bytes");
      saveBytes(dataPath("") + "/JPGHex.jpg", savebytes2);
      img = loadImage(dataPath("") + "/JPGHex.jpg");
    }

    canvas.endDraw();
  }
  byte[] fromHexString(final String encoded) {
    if ((encoded.length() % 2) != 0)
      throw new IllegalArgumentException("Input string must contain an even number of characters");

    final byte result[] = new byte[encoded.length()/2];
    final char enc[] = encoded.toCharArray();
    for (int i = 0; i < enc.length; i += 2) {
      StringBuilder curr = new StringBuilder(2);
      curr.append(enc[i]).append(enc[i + 1]);
      result[i/2] = (byte) Integer.parseInt(curr.toString(), 16);
    }
    return result;
  }
}

class WEBPCORRUPTION extends Shader {
  PImage img;
  byte[] brokenfile;
  int rw, rh;
  String[] convertToPNG;
  String[] convertToWebp;
  WEBPCORRUPTION() {
    name = "fxWebpCorruption";
    params.add(new Param("byte amount to change probability", INTVAL, 2, 50, new int[]{RANDOM}));
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 1;
    rw = canvas.width;
    rh = canvas.height;
    img = createImage(rw, rh, ARGB);
    convertToPNG = new String[]{"/usr/local/bin/convert", "-verbose", dataPath("") + "/webp_glitched.webp", dataPath("") + "/webp_glitched.png"};
    convertToWebp = new String[]{"/usr/local/bin/convert", "-verbose", dataPath("") + "/webp_orig.png", dataPath("") + "/webp_orig.webp"};
  }

  void apply() {
    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      img.resize(rw, rh);
    }
    int probparam = (int)params.get(0).getValue();
    img = canvas.get();
    canvas.beginDraw();
    canvas.image(img, canvas.width/2, canvas.height/2);


    File oldImage = new File(dataPath("")+"/webp_orig.png");
    if (oldImage.exists()) {
      oldImage.delete();
    }
    oldImage = new File(dataPath("")+"/webp_orig.webp"); //is it better to move this up again?
    if (oldImage.exists()) {
      oldImage.delete();
    }
    oldImage = new File(dataPath("")+"/webp_glitched.webp");
    if (oldImage.exists()) {
      oldImage.delete();
    }
    oldImage = new File(dataPath("")+"/webp_glitched.png");
    if (oldImage.exists()) {
      oldImage.delete();
    }
    canvas.save(dataPath("")+"/webp_orig.png"); //save as png
    int failCount = 0;
    while ((runCommand(convertToPNG) != 0) && failCount < 25) {
      failCount ++;
      println("Trying to convert...");
      while (runCommand(convertToWebp) != 0) {
        println("Conversion failed, trying again...");
      }
      brokenfile = loadBytes(dataPath("")+"/webp_orig.webp"); //and reload. just in case it wasnt a jpg.
      byte[] savebytes = new byte[brokenfile.length];
      double probability = float(probparam) / float(brokenfile.length);
      int glitchCount = 0;
      for (int i = 0; i < brokenfile.length; i++) {
        String hexStr = hex(brokenfile[i]);
        if (i > 10) { // skip header
          if (random(1)<probability) {
            String randomHex = (str(Character.forDigit((int)random(16), 16)) + str(Character.forDigit((int)random(16), 16))).toUpperCase();
            hexStr = randomHex;
            glitchCount++;
          }
        }
        byte hexByte[] = fromHexString(hexStr);
        for (int j = 0; j < hexByte.length; j++) {
          savebytes[i] += hexByte[j];
        }
      }
      println("Glitched " + glitchCount + " bytes");
      saveBytes(dataPath("") + "/webp_glitched.webp", savebytes);
      File newImage = new File(dataPath("")+"/webp_glitched.webp");
      while (!newImage.exists()) {
        println("Waiting until bytes have been written to disk...");
      }
      /*
    String[] convertToPNG = {"/usr/local/bin/convert", "-verbose", dataPath("") + "/webp_glitched.webp", dataPath("") + "/webp_glitched.png"};
       while (!runCommand(convertToPNG)) {
       println("Trying to convert...");
       }
       */
    }
    if (failCount == 25) println("Failed 50 times, skipping webp glitch");
    else img = loadImage(dataPath("") + "/webp_glitched.png");

    //launch(dataPath("") + "/webpdecode.command");
    //delay(1500);
    //try or load fallback image and redo in while loop
    //PImage compare = img.get();
    //img = loadImage(dataPath("") + "/result.png");

    canvas.image(img, canvas.width/2, canvas.height/2);
    canvas.endDraw();
  }

  byte[] fromHexString(final String encoded) {
    if ((encoded.length() % 2) != 0)
      throw new IllegalArgumentException("Input string must contain an even number of characters");

    final byte result[] = new byte[encoded.length()/2];
    final char enc[] = encoded.toCharArray();
    for (int i = 0; i < enc.length; i += 2) {
      StringBuilder curr = new StringBuilder(2);
      curr.append(enc[i]).append(enc[i + 1]);
      result[i/2] = (byte) Integer.parseInt(curr.toString(), 16);
    }
    return result;
  }
}



/*

 SOX
 
 */

class SOX extends Shader {
  PImage img;
  int rw, rh;
  int depth = 8;
  int iterations = 1;
  int samplerate = 44100;
  int channels = 1;
  String coding = "a-law";
  String type = "al";
  int encoding = 0;
  boolean rgbyuv = true;
  boolean do_blend = false;
  int blend_type = 1;
  String[] effectParams;
  SOX() {
  }
  void setSoxParams() {
    rw = canvas.width;
    rh = canvas.height;
    img = createImage(rw, rh, ARGB);
    params.add(new Param("direction", INTVAL, 0, 3, new int[]{RANDOM}));
    directionParamIndex = 0;
    params.add(new Param("iterations", INTVAL, 1, 10, new int[]{RANDOM}));
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("blend type", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param("bitdepth (8, 16, 24)", INTVAL, 1, 3, new int[]{RANDOM}));
    params.add(new Param("samplerate", INTVAL, 1400, 200000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("encoding", INTVAL, 0, 7, new int[]{RANDOM}));
    params.add(new Param("yuv / rgb", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("channels (mono / stereo)", INTVAL, 1, 2, new int[]{RANDOM, SQUAR}));
  }

  void apply() {
    iterations = (int)params.get(1).getValue();
    do_blend = boolean((int)params.get(2).getValue());
    blend_type = blends[(int)params.get(3).getValue()];
    depth = (int)params.get(4).getValue() * 8;
    samplerate = (int)params.get(5).getValue();
    encoding = (int)params.get(6).getValue();
    switch(encoding) {
      case(0):
      type = "s8";
      coding = "signed-integer";
      break;
      case(1):
      type = "u8";
      coding = "unsigned-integer";
      break;
      case(2):
      type = "s16";
      coding = "signed-integer";
      break;
      case(3):
      type = "u16";
      coding = "unsigned-integer";
      break;
      case(4):
      type = "s24";
      coding = "signed-integer";
      break;
      case(5):
      type = "u24";
      coding = "unsigned-integer";
      break;
      case(6):
      type = "al";
      coding = "a-law";
      break;
      case(7):
      type = "ul";
      coding = "u-law";
      break;
    default:
      type = "f32";
      coding = "float";
      break;
    }
    rgbyuv = boolean((int)params.get(7).getValue());
    channels = (int)params.get(8).getValue();
    getParams();

    if (rw != canvas.width || rh != canvas.height) {
      rw = canvas.width;
      rh = canvas.height;
      img.resize(rw, rh);
    }
    img = canvas.get();
    canvas.beginDraw();
    canvas.image(img, canvas.width/2, canvas.height/2);
    File oldImage = new File(dataPath("")+"/sox.raw");
    if (oldImage.exists()) {
      oldImage.delete();
    }
    oldImage = new File(dataPath("")+"/sox.png");
    if (oldImage.exists()) {
      oldImage.delete();
    }
    oldImage = new File(dataPath("")+"/sox_sonified.raw");
    if (oldImage.exists()) {
      oldImage.delete();
    }
    canvas.save(dataPath("")+"/sox.png"); //save as png

    String[] convertToRAW = new String[]{"/usr/local/bin/convert", "-verbose", dataPath("") + "/sox.png", "-depth", str(depth), ((rgbyuv) ? "rgb" : "yuv") + ":" + dataPath("") + "/sox.raw"};
    String[] soxCall = new String[]{"/usr/local/bin/sox", "-r", str(samplerate), "-e", coding, "-b", "8", "-c", "2", "-t", type, dataPath("") + "/sox.raw", dataPath("") + "/sox_sonified.raw"};
    String[] sonify = concat(soxCall, effectParams);
    String[] convertToPNG = new String[]{"/usr/local/bin/convert", "-verbose", "-size", str(rw) + "x" + str(rh), "-depth", str(depth), ((rgbyuv) ? "rgb" : "yuv") + ":" + dataPath("") + "/sox_sonified.raw", dataPath("") + "/sox.png"};

    for (int i = 0; i < iterations; i++) {
      if (runCommand(convertToRAW) != 0) {
        println("Conversion to RAW failed");
      }
      if (runCommand(sonify) != 0) {
        println("Sonification failed");
      }
      if (runCommand(convertToPNG) != 0) {
        println("Conversion to PNG failed");
      }
      img = loadImage(dataPath("") + "/sox.png");
    }

    if (do_blend) {
      canvas.blend(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height, blend_type);
    } else {
      canvas.image(img, canvas.width/2, canvas.height/2);
    }
    canvas.endDraw();
  }
  void getParams() {
  }
}



/*

 SOXECHO
 
 
 NEEDS FIXING
 
 */

class SOXECHO extends SOX {
  float gainin = 0.8;
  float gainout = 0.8;
  float delay = 0.8;
  float decay = 0.8;
  SOXECHO() {
    name = "fxSoxEcho";
    setSoxParams();
    params.add(new Param("gain in", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("gain out", FLOATVAL, 0.01, 1000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("delay", FLOATVAL, 0.01, 1000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("decay", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }

  void getParams() {
    gainin = params.get(9).getValue();
    gainout = params.get(10).getValue();
    delay = params.get(11).getValue();
    decay = params.get(12).getValue();
    effectParams = new String[]{"echo", str(gainin), str(gainout), str(delay), str(decay)};
  }
}

/*

 SOXALLPASS
 
 */

class SOXALLPASS extends SOX {
  String hkqo = "h";
  int frequency = 100;
  int widt = 100;

  SOXALLPASS() {
    name = "fxSoxAllpass";
    setSoxParams();
    params.add(new Param("frequency", INTVAL, 1, 1000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("width", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h/k/q/o", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }
  void getParams() {
    frequency = (int)params.get(9).getValue();
    widt = (int)params.get(10).getValue();
    switch((int)params.get(11).getValue()) {
      case(0):
      hkqo = "h";
      break;
      case(1):
      hkqo = "k";
      break;
      case(2):
      hkqo = "q";
      break;
    default:
      hkqo = "o";
      break;
    }
    effectParams = new String[]{"allpass", str(frequency), str(widt) + hkqo};
  }
}

/*

 SOXBAND
 
 */

class SOXBAND extends SOX {
  String hkqo = "h";
  int center = 100;
  int widt = 100;
  boolean n;

  SOXBAND() {
    name = "fxSoxBand";
    setSoxParams();
    params.add(new Param("center", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("width", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h/k/q/o", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("-n", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
  }

  void getParams() {
    center = (int)params.get(9).getValue();
    widt = (int)params.get(10).getValue();
    switch((int)params.get(11).getValue()) {
      case(0):
      hkqo = "h";
      break;
      case(1):
      hkqo = "k";
      break;
      case(2):
      hkqo = "q";
      break;
    default:
      hkqo = "o";
      break;
    }
    n = boolean((int)params.get(12).getValue());
    effectParams = new String[]{ "band", (n ? "-n " : "") + str(center), str(widt) + hkqo};
  }
}


/*

 SOXBANDPASS
 
 */

class SOXBANDPASS extends SOX {
  int widt, frequency;
  String hkqo;
  boolean c;
  SOXBANDPASS() {
    name = "soxBandPass";
    setSoxParams();
    params.add(new Param("frequency", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("width", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h/k/q/o", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("-c", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
  }
  void getParams() {
    frequency = (int)params.get(9).getValue();
    widt = (int)params.get(10).getValue();
    switch((int)params.get(11).getValue()) {
      case(0):
      hkqo = "h";
      break;
      case(1):
      hkqo = "k";
      break;
      case(2):
      hkqo = "q";
      break;
    default:
      hkqo = "o";
      break;
    }
    c = boolean((int)params.get(12).getValue());

    effectParams = new String[]{"bandpass", (c ? "-c " : "") + str(frequency), str(widt) + hkqo};
  }
}


/*

 SOXBANDREJECT
 
 */

class SOXBANDREJECT extends SOX {
  int widt, frequency;
  String hkqo;
  SOXBANDREJECT() {
    name = "soxBandReject";
    setSoxParams();
    params.add(new Param("frequency", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("width", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h/k/q/o", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }
  void getParams() {
    frequency = (int)params.get(9).getValue();
    widt = (int)params.get(10).getValue();
    switch((int)params.get(11).getValue()) {
      case(0):
      hkqo = "h";
      break;
      case(1):
      hkqo = "k";
      break;
      case(2):
      hkqo = "q";
      break;
    default:
      hkqo = "o";
      break;
    }

    effectParams = new String[]{"bandreject", str(frequency), str(widt) + hkqo};
  }
}



/*

 SOXBASS
 
 */

class SOXBASS extends SOX {
  int widt, frequency, gain;
  float seconds;
  String hkqo;
  SOXBASS() {
    name = "soxBass";
    setSoxParams();
    params.add(new Param("gain", INTVAL, 1, 100, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("frequency", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("width", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h/k/q/o", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("seconds", FLOATVAL, 0.001, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }
  void getParams() {
    gain = (int)params.get(9).getValue();
    frequency = (int)params.get(10).getValue();
    widt = (int)params.get(11).getValue();
    switch((int)params.get(12).getValue()) {
      case(0):
      hkqo = "h";
      break;
      case(1):
      hkqo = "k";
      break;
      case(2):
      hkqo = "q";
      break;
    default:
      hkqo = "o";
      break;
    }
    seconds = params.get(13).getValue();

    effectParams = new String[]{"bass", str(gain), str(frequency), str(widt) + hkqo};
  }
}

/*

 SOXBEND
 
 */

class SOXBEND extends SOX {
  int framerate, oversample, start, cents, end;
  SOXBEND() {
    name = "soxBend";
    setSoxParams();
    //params.add(new Param("frequency", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    //params.add(new Param("width", INTVAL, 1, 10000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    //params.add(new Param("h/k/q/o", INTVAL, 0, 3, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }
  void getParams() {
    //frequency = (int)params.get(9).getValue();
    //widt = (int)params.get(10).getValue();

    //effectParams = new String[]{"bend", str(frequency), str(widt) + hkqo};
  }
}
