Skip to content

CM 2

CM-2 "Random and Pleasing"

The CM-2 at MoMA is running my simulation of "Random and Pleasing", based on recollections by Dan Aronson and Tamiko Thiel, using LED panels from BlinkinLabs (who also have 8x8 wearable badges). More CM-2 reference photos.

/*
     * CM-2 random and pleasing simulator.
     *
     * Each cube has 20 slots with 1" spacing, 8 LEDs, 4 I/O, 8 LEDs.
     * Each LED board has 32 vertical LEDs with 0.5" spacing.
     * Reference photo: https://www.flickr.com/photos/osr/32907925763/in/album-72157680241484140/
     * Description from Dan Aronson:

Cliff lasser did the version for the cm1, I did the version for the cm2.
No communication. During the microprocessor idle loop there was code to
step through memory and then send the bitwise or of the memory for each
processor on the chip to the led. All random and pleasing did for the cm2
was to zero out all memory and them randomize each bit of memory (1 or 0)
in the memory for processor 0 on each chip (and the idle loop would take
care of the rest).

Pretty sure that p was 50% when I first adapted this, I would try t between
100 and 250 milliseconds.

     */
float threshold = 0.40;  // number to have on
float led_fade = 0.0; // no fade by default
float led_dt = 100; // dan suggests between 4 and 10 Hz, tamiko suggests faster
boolean show_controls = false;

float t;
float x_spacing = 25.4;
float y_spacing = 15.8;
float led_diameter = 11;
float cube_size = (20 + 2 * 4) * x_spacing;

Button p_sub = new Button(10, 20, "P-");
Button p_add = new Button(40, 20, "P+");

Button t_sub = new Button(110, 20, "T-");
Button t_add = new Button(140, 20, "T+");

Button f_sub = new Button(210, 20, "F-");
Button f_add = new Button(240, 20, "F+");

class LedArray
{
        static final int num_x = 8;
        static final int num_y = 32;
        float[][] bright = new float[num_x](num_x)[num_y](num_y);
        boolean[][] leds = new boolean[num_x](num_x)[num_y](num_y);

        void draw()
        {
            fill(255,0,0);
            noStroke();

            for(int x = 0 ; x < 8 ; x++)
            {
                for(int y = 0; y < 32; y++)
                {
                    // fade out if the pixel is off, ramp up when they come on
                    if(!leds[x](x)[y](y))
                    {
                        bright[x](x)[y](y) *= led_fade;
                                    } else {
                                            //bright[x](x)[y](y) += 0.6;  // ramp up
                                            bright[x](x)[y](y) = 1.0; // immediate on
                    }

                    fill(255*bright[x](x)[y](y),0,0);
                    ellipse(x*x_spacing, y*y_spacing, led_diameter, led_diameter);
                }
            }
        }

        void update(float threshold)
        {
            for(int x = 0 ; x < 8 ; x++)
            {
                for(int y = 0; y < 32; y++)
                {
                    float r = random(1.0);
                    if (r > threshold)
                    {
                        // this pixel is off, fade it out
                        leds[x](x)[y](y) = false;
                    } else {
                        // this pixel is now on
                        leds[x](x)[y](y) = true;
                        //bright[x](x)[y](y) = 1.0;
                    }
                }
            }
        }

}

class Cube
{
        LedArray a = new LedArray();
        LedArray b = new LedArray();

        void draw(float threshold)
        {
            pushMatrix();
            noStroke();

            // draw the dark background
            fill(20,0,0);
            rect(-4*x_spacing,-4*x_spacing, (20+8)*x_spacing, (20+8)*x_spacing);

            a.draw();

            translate(12*x_spacing,0);
            b.draw();

            popMatrix();
        }

        void update(float threshold)
        {
            a.update(threshold);
            b.update(threshold);
        }
}


Cube[] cubes = new Cube[4](4);

void setup()
{
        size(600,600, P2D);
        frameRate(30);
        //frameRate(5);


        for(int i = 0 ; i < 4 ; i++)
            cubes[i](i) = new Cube();
}

float last_update;
int frames = 0;

void mouseClicked()
{
        float mx = mouseX;
        float my = mouseY;

        if (p_add.hit(mx,my))
        {
            threshold += 0.05;
            if(threshold > 1) threshold = 1;
        } else
        if (p_sub.hit(mx,my))
        {
            threshold -= 0.05;
            if (threshold < 0) threshold = 0;
        } else

        if (t_add.hit(mx,my))
        {
            led_dt += 10;
            if(led_dt > 1000) led_dt = 1000;
        } else
        if (t_sub.hit(mx,my))
        {
            led_dt -= 10;
            if (led_dt < 0) led_dt = 0;
        } else

        if (f_add.hit(mx,my))
        {
            led_fade += 0.05;
            if(led_fade > 1) led_fade = 1;
        } else
        if (f_sub.hit(mx,my))
        {
            led_fade -= 0.05;
            if (led_fade < 0) led_fade = 0;
        } else

        {
            // toggle the controls on and off
            show_controls = !show_controls;
        }

}

void draw()
{
        background(0);

        if (show_controls)
        {
            p_add.draw(); p_sub.draw();
            t_add.draw(); t_sub.draw();
            f_add.draw(); f_sub.draw();

            text(str(int(threshold*100)), 70, 20);
            text(str(int(led_dt)), 170, 20);
            text(str(int(led_fade*100)), 270, 20);
        }

        pushMatrix();
        translate(80,80);
            scale(0.33);

        float now = millis();
        if (now - last_update > led_dt)
        {
                    last_update = now;
            for(int i = 0 ; i < 4 ; i++)
                cubes[i](i).update(threshold);
        }


        for(int x = 0 ; x < 2 ; x++)
        {
            for(int y = 0 ; y < 2 ; y++)
            {
                pushMatrix();
                translate(x*cube_size*1.2,y*cube_size*1.2);
                cubes[x*2+y](x*2+y).draw(threshold);
                popMatrix();
            }
        }
/*
        // sine ramping looks nice, but is not historical
        threshold = (sin(t) + 1.0) / 4.0 + 0.2; // never all off, never all on
        t += 0.005;

*/
        popMatrix();

        if (false)
        {
            saveFrame("randompleasing-######.png");
            if(frames++ > 99)
                exit();
        }
}


class Button
{
        float x, y, w, h;
        String s;

        Button(float xpos, float ypos, String initial_text)
        {
            x = xpos;
            y = ypos;
            h = 18;
            s = initial_text;
        }

        void draw()
        {
            textSize(h);
            textAlign(CENTER, CENTER);

            w = textWidth(s);

            pushMatrix();
            translate(x,y);
            stroke(255);
            fill(10);
            rect(-w/2-4, -h/2-4, w+8, h+8);

            fill(255);
            text(s, 0, 0);
            popMatrix();
        }

        boolean hit(float mx, float my)
        {
            if (mx < x - w/2 || x + w/2 < mx)
                        return false;
            if (my < y - h/2 || y + h/2 < my)
                         return false;
                    return true;
        }
}

2017 Retrocomputing Art


Last update: November 8, 2020