General program practices for an "effects gallery"

I currently have a sketch that reproduces what's drawn on the screen and displays it on a LED matrix.

My idea for this sketch is to draw a layered gallery of effects (simple realtime Processing made animations), but I'm not sure of how to store these effects (each one being a little portion of code).

I need to:

  • Store collection of effects sequences
  • Each collection stores a sequence of layered animations ("layered animations" means up to 4 animations running on top of each other
  • Each animation has variables that can be modified by the user (as speed, hue, etc.)

I know that the animations can be stored as functions and called when necessary, ordered by "layer". And maybe each collection can be also a function that calls the needed animations.

What structure do you recommend? I'm fairly new in Processing and Java, so I don't know the best practices for this scenario. Maybe storing the animations on a separate file and importing it? (to mantain a cleaner code)

Answers

  • First things first: Don't bother too much about using multiple files. Processing treats different .pde files as if they were all one large file anyway, so as long as you can manage to keep everything organized in one file, that's fine.

    Next, each animation is going to need, at the very least, a function. There is just no getting around this, because each animation needs to be able to do some drawing, and thus it needs to run some drawing code, and thus is needs to be in a function that draw() can call. Each animation may have other things it needs - global variables, user inputted values, data loaded from other files, etc - but let's not worry about those yet. Instead, let's write you a few animation functions now, just as place holders:

    // One Liners
    color rcolor(){return(color(random(255),random(255),random(255)) );}
    
    // Animation Functions - each draws a frame for a given animation.
    void ani_checkerBoard(){
      background(0);
      fill(255);
      noStroke();
      for(int i = 0; i < width/10; i++){
        for( int j = 0; j < height / 10; j++ ){
          if( (i+j)%2 == ((millis()%5000<2500)?0:1) ){
            rect(10*i,10*j,10,10);
          }
        }
      }
    }
    
    void ani_randomRects(){
      stroke(0);
      fill(rcolor());
      rect(random(width),random(height),10,10);
    }
    
    void ani_spinningEllipse(){
      stroke(0);
      fill(rcolor());
      pushMatrix();
      translate(200,200);
      rotate(map(millis()%7000,0,7000,0,TWO_PI));
      ellipse(0,100,60,60);
      popMatrix();
    }
    
    // Classic setup() and draw(), demo the animations.
    void setup(){
      size(400,400);
      ani_checkerBoard();
    }
    
    void draw(){
      //ani_checkerBoard();
      ani_randomRects();
      ani_spinningEllipse();
    }
    
  • This is great, but what you really want is a collection. So what is a collection? You said it yourself - it's up to four animations that run at the same time. Since each animation has a unique function name, the easiest way to track which animations are in a collection is to just remember the four animation function names that a collection needs to call.

    There's a little black magic here, in the use of the method() function.

    // One Liners
    color rcolor() {
      return(color(random(255), random(255), random(255)) );
    }
    
    // Animation Functions - each draws a frame for a given animation.
    void ani_checkerBoard() {
      background(0);
      fill(255);
      noStroke();
      for (int i = 0; i < width/10; i++) {
        for ( int j = 0; j < height / 10; j++ ) {
          if ( (i+j)%2 == ((millis()%5000<2500)?0:1) ) {
            rect(10*i, 10*j, 10, 10);
          }
        }
      }
    }
    
    void ani_randomRects() {
      stroke(0);
      fill(rcolor());
      rect(random(width), random(height), 10, 10);
    }
    
    void ani_newBackground() {
      background(rcolor());
    }
    
    void ani_spinningEllipse() {
      stroke(0);
      fill(rcolor());
      pushMatrix();
      translate(200, 200);
      rotate(map(millis()%7000, 0, 7000, 0, TWO_PI));
      ellipse(0, 100, 60, 60);
      popMatrix();
    }
    
    // Define some collections.
    String[] col_abstract = { "ani_checkerBoard", "ani_spinningEllipse" }; 
    String[] col_jazzy = { "ani_newBackground", "ani_randomRects" };
    // Track which collection to draw.
    boolean draw_jazzy = false;
    
    // Setup().
    void setup() {
      size(400, 400);
    }
    
    // Draw().
    void draw() {
      if ( draw_jazzy ) {
        for ( int i = 0; i < col_jazzy.length; i++) {
          method(col_jazzy[i]);
        }
      } else {
        for ( int i = 0; i < col_jazzy.length; i++) {
          method(col_abstract[i]);
        }
      }
    }
    
    // Toggle collection drawn with mouse click.
    void mousePressed() {
      draw_jazzy = !draw_jazzy;
    }
    

    This is a lot better, but it's not perfect yet.

  • edited February 2018

    This, however, is.

    // One Liners
    color rcolor() {
      return(color(random(255), random(255), random(255)) );
    }
    
    // Animation Functions - each draws a frame for a given animation.
    void ani_checkerBoard() {
      background(0);
      fill(255);
      noStroke();
      for (int i = 0; i < width/10; i++) {
        for ( int j = 0; j < height / 10; j++ ) {
          if ( (i+j)%2 == ((millis()%5000<2500)?0:1) ) {
            rect(10*i, 10*j, 10, 10);
          }
        }
      }
    }
    
    void ani_randomRects() {
      stroke(0);
      fill(rcolor());
      rect(random(width), random(height), 10, 10);
    }
    
    void ani_newBackground() {
      background(rcolor());
    }
    
    void ani_spinningEllipse() {
      stroke(0);
      fill(rcolor());
      pushMatrix();
      translate(200, 200);
      rotate(map(millis()%7000, 0, 7000, 0, TWO_PI));
      ellipse(0, 100, 60, 60);
      popMatrix();
    }
    
    void ani_dotAtMouse(){
      fill(0);
      ellipse(mouseX+5,mouseY+10,50,50);
    }
    
    // Define a struction for all collections.
    String[][] collections = {
      { "ani_checkerBoard", "ani_spinningEllipse" }, // "abstract" - ellipse spinning over checkerboar 
      { "ani_newBackground", "ani_randomRects", "ani_dotAtMouse" }, // "jazzy" - flashy colors and rects, black spot at mouse.
    }; // the end of all collections.
    // Track which collection to draw.
    int current_collection = 0;
    
    // Setup().
    void setup() {
      size(400, 400);
    }
    
    // Draw().
    void draw() {
      for ( int i = 0; i < collections[current_collection].length; i++) {
        method(collections[current_collection][i]);
      }
    }
    
    // Toggle collection drawn with mouse click.
    void mousePressed() {
      current_collection++;
      current_collection%=collections.length;
    }
    

    Notice that all the collections are now in one large 2D array of strings. The current collection is recorded by its index, stored in the current_collection variable. the draw() function just draws each of the functions in the current collection.

    You can easily add more animation functions to this. You can also easily add more collections by specifying their animation functions by name.

    The only problem here is that you need to make sure that you don't mix up global variables between different animations. For example, if two different animations are using a variable called x, well, there's only one global x variable for them to use - so they'd share it. This could be solved by making each animation function into it's own object, but that is probably too complex unless you have animations that are sharing numerous variable names.

    Of course, there's still a lot to do. If any animations need resources loading, you need to make sure that happens in setup(). Plus you will need to check which collection is running if there is any sort of user input happening.

    Let us know how it turns out. If you have more questions, post your complete working sketch with attempted modifications when you ask.

  • Wow, great! Thank you very much. It's a great start. I don't have the animations ready yet as to test throughly this method, but for as I can see, there are some things I'm struggling to understand how to implement:

    • These functions we're calling through method() need to have some parameters to "configure" them for each collection, some (if not all) of the animations are present on different collections, but with different parameters (different hue or speed, for example). Can I call these functions with the parameters values included through method()?
    • One thing I failed to explain before was that the collections are sequences of "sub-collections", let's say, inside a collection there's a group of sub-collections (up to 4 layered animations) that can be switched to the next by user input. I think I can solve this by myself though, by adding a level of hierarchy of arrays.

    I'll do some tests, try to figure it out myself (I like learning this way), and post back when I have a code to show. But this is a great start, thank you.

  • 1) Alas, you can't call a function with parameters using method(). Instead, you have to put the values you do want to pass into global variables that the called function then reads. Example:

    int g_x;
    String g_s;
    
    void showThings(){
      println( g_x );
      println( g_s );
    }
    
    void setup(){
      g_x = 4;
      g_s = "four";
      method( "showThings");
      g_x = 5;
      g_s = "five";
      method( "showThings");
    }
    
    void draw(){}
    

    2) Interesting. try it yourself.

  • This, to me, suggests classes. And an interface defining the common methods. And an ArrayList.

    Instead of a clutch of global variables the variables would being to the class. Neater that way.

    The user aspect is a bit difficult though. How is the user changing these variables? And if you have 4 different animations with 3 or 4 user tweakable variables each, that's a lot of stuff

  • Yeah, @koogs, I know, classes sounds like they should help here, but since each animation is doing something different to generate its image, it's not immediately obvious how you would do it.

    If I were writing this in, say, Ruby, there would be no problem - each animation would just have a string that can be treated as trusted code and then simply executed. But there's no easy way to run arbitrary code like that in Processing.

    Maybe a base Animation class which each specific animation inherits from? Bleh. That's still a mess.

    Give it some thought? I have a few things to try.

  • edited February 2018

    This probably won't be any help to maurobarreca, but in JRubyArt we can simply write:-

    attr_reader :g_x, :g_s
    
    def show_things
      puts g_x
      puts g_s
    end
    
    def setup
      @ g_x = 4
      @ g_s = 'four'
      show_things
      @ g_x = 5
      @ g_s = 'five'
      show_things
    end
    
    def draw
    
    end
    

    What is more in watch mode you could edit sketch on fly and have it re-draw

  • since each animation is doing something different to generate its image, it's not immediately obvious how you would do it.

    Depends on the animation, I guess. My immediate thought, because he called them "effects", was that they were all filters, filtering the results of the previous operations. So, pixels in, pixels out. But, yes, could be anything.

    The introspective nature of method(), however nicely it's hidden from the user, bugs me. If you're using a strongly typed language then use it, don't start passing function names around as strings.

  • edited February 2018

    If you're using a strongly typed language then use it, ...

    Although main Processing Mode is Java, all the other flavors are made for weakly-typed languages! ~O)

    Processing isn't bound to Java at all. B-)

    Processing has no obligation to please hardcore Java programmers either! :P

  • Depends on the animation, I guess. My immediate thought, because he called them "effects", was that they were all filters, filtering the results of the previous operations. So, pixels in, pixels out. But, yes, could be anything.

    Sorry, maybe I don't use the correct terms because English is my second language. The idea is fairly simple: you have a series of animations, this is a simple example of one:

    void palomaBass(int bpm, int r, int g, int b){
      long xt = t-rt; //diferencia entre t y rt
      long bpmt = 60000/bpm; //duracion de un beat
      if(xt>bpmt){
        fill(r,g,b);
        rect(0,0,matrixSizeX,matrixSizeY);
        rt = t;
      }
    }
    

    In this example palomaBass is nested inside a "layers" function (let's call it "scene"). Like palomaBass, there's maybe other 3 animations called inside the scene. The idea is that the user can modify r, g and b for example with a MIDI controller. All this is working now, what I'm trying to figure out is how to manage these "collections".

    I need to store each scene inside another function, a "sequence" function, that calls these scenes when intended. And then I need to easily call these "sequences" when intended.

    Basically I want to navigate forward and backwards from sequence to sequence, and inside each sequence, from scene to scene, that's the part I'm not sure how to solve.

  • edited February 2018
    • Inside palomaBass(), you're accessing unknown variables called t & rt.
    • And even worse: rt is not only accessed but modified as well! :-SS
    • If by chance they happen to be indeed "global" variables, you should create a class for them instead.
    • And make palomaBass() a method of that class. *-:)
  • (there's some talk here: https://forum.processing.org/two/discussion/26290/how-to-declare-a-variable-once-inside-a-loop but it's basically a duplicate of mauro's last post with less context.)

  • edited February 2018

    Inside palomaBass(), you're accessing unknown variables called t & rt.

    Yes, they're global. t is stores the value of millis() on each draw loop, but rt stores a value of millis() when a condition is met inside palomaBass(). As koogs says, I asked a question outside of this thread for how to make rt stop being global and declare it inside palomaBass() (the problem here is that declaring it inside will 0 it out everytime the function is called on a loop, so it's modification inside the loop is useless).

    I will check out if I can make this all work with classes, objects and methods, it will be my first time using those resources from scratch.

  • edited February 2018

    Should I do something like this?:

    class Paloma {
       long t = millis();
       long rt = 0;
       void bass(int bpm, int r, int g, int b){
          long xt = t-rt; //diferencia entre t y rt
          long bpmt = 60000/bpm; //duracion de un beat
          if(xt>bpmt){
             fill(r,g,b);
             rect(0,0,matrixSizeX,matrixSizeY);
             rt = t;
          }
       }
    }
    

    And then call it with:

    Paloma.bass(120,125,255,0);

    (I'm ignoring the declaring and initiation of this object for this example)

    Is this how it works?

  • edited February 2018

    Almost there! But I can spot you're still accessing "global" variables inside your class Paloma: matrixSizeX & matrixSizeY. :-\"

    Notice that millis() returns an int value. No need for a long variable to store it: L-)
    https://Processing.org/reference/millis_.html

    B/c millis() current value increases all the time, we need to re-invoke it every time, not just once. :-B

    Rather than r, g & b, why not the whole aRGB color as in int variable? O:-)

    You can also store the desired color as a field member of your class too, rather than keeping passing it to bass() method over & over. :ar!

    Much probably you're gonna need to store the coordinates for rect() as fields too. >-)

    Here are some modifications I've come up w/ for your Paloma class: :D

    class Paloma {
      static final int DIAM = 10, BPM = 60_000;
    
      int x, y, bpmt, pm = millis();
      color c;
    
      Paloma(int px, int py, color cc, int bpm) {
        x = px;
        y = py;
        c = cc;
        setBPM(bpm);
      }
    
      int setBPM(int bpm) {
        return bpmt = BPM/bpm;
      }
    
      Paloma bass() {
        final int m = millis(), diff = m - pm;
    
        if (diff >= bpmt) {
          pm = m;
          fill(c);
          rect(x, y, DIAM, DIAM);
        }
    
        return this;
      }
    
      @ Override String toString() {
        return "x: " + x + ", y: " + y + ", millis: " + pm;
      }
    }
    
  • edited February 2018

    Thank you!

    Almost there! But I can spot you're still accessing "global" variables inside your class Paloma: matrixSizeX & matrixSizeY.

    Yes, it's handy for this sketch to have those global variables there. They're constant, so it's just for configuration. I prefer to have them there than having to input them every time I call an object. Having globals there is a functional problem or a style one? I'll check your other recommendations, thank you very much.

  • edited February 2018

    They're constant, ...

    So they should follow ALL_CAPS naming convention. L-)
    Also, if they're related to some class, they should be placed there. *-:)

Sign In or Register to comment.