/** ConcentricCircles (save as ConcentricCirclesPShape). STUDENT NAME AND NOTES: ASSIGNMENT DUE BY END OF March 31, 2017 via D2L. Nesting example to get a 3D like look from 2D circles, D. Parson, Feb 24, 2017. You could stack up other shapes or PNG or SVG files. INTERACTION: Up arrow adds concentric circles Down reduces them. Right TBD by student. Cycles up through MIDI patches for voice. Left TBD by student. Cycles down through MIDI patches for voice. Mouse position on screen determines offset of nested shapes. Left to right mouse sets pitch, bottom to top sets volume. Currently this appears to grow towards the viewer because smaller circles can go outside the edge of the original circle. If you plotted them from smallest to biggest, with the bigegst plotted last, it might reverse this visual effect. THIS STUTTERS A LITTLE IN TRACKING THE MOUSE DUE TO THE AMOUNT OF COMPUTATION. THERE ARE WAYS TO SPEED IT UP. Assignment 3 uses PShape to do that. **/ import javax.sound.midi.* ; // Get everything in the Java MIDI (Musical Instrument Digital Interface) package. //GRAPHICS VARIABLES int nestCircles = 10 ; final int limitNestCircles = 100 ; int strokew8 = 4 ; final int strokeLimit = 10 ; final int numLocations = 10 ; int frameCounter ; // MIDI VARIABLES, see http://faculty.kutztown.edu/parson/spring2017/MidiKBSpring2016Parson.txt final int midiDeviceIndex = 0 ; // setup() checks for number of devices. Use one for output. // NOTE: A final variable is in fact a constant that cannot be changed. MidiDevice.Info[] midiDeviceInfo = null ; // See javax.sound.midi.MidiSystem and javax.sound.midi.MidiDevice MidiDevice device = null ; // See javax.sound.midi.MidiSystem and javax.sound.midi.MidiDevice Receiver receiver = null ; // javax.sound.midi.Receiver receives your OUTPUT MIDI messages (counterintuitive?) // SEE https://www.midi.org/specifications/item/gm-level-1-sound-set but start at 0, not 1 int voice = 73 ; // flute per above link, change with up & down arrow keys int lastnote = -1 ; // most recent midi note in the range 0..127 int lastvolume = -1 ; // most recent midi note in the range 0..127 void setup() { // GRAPHICS: fullScreen(P2D); // P2D uses OpenGL renderer for speed; order of PShape geometric transforms is reversed. ellipseMode(CENTER); rectMode(CENTER); background(255); stroke(0); noFill(); ellipseMode(CENTER); rectMode(CENTER); frameRate(10); // MIDI: // 1. FIND OUT WHAT MIDI DEVICES ARE AVAILABLE FOR VARIABLE midiDeviceIndex. midiDeviceInfo = MidiSystem.getMidiDeviceInfo(); for (int i = 0 ; i < midiDeviceInfo.length ; i++) { println("MIDI DEVICE NUMBER " + i + " Name: " + midiDeviceInfo[i].getName() + ", Vendor: " + midiDeviceInfo[i].getVendor() + ", Description: " + midiDeviceInfo[i].getDescription()); } // 2. OPEN ONE OF THE MIDI DEVICES UP FOR OUTPUT. try { device = MidiSystem.getMidiDevice(midiDeviceInfo[midiDeviceIndex]); device.open(); // Make sure to close it before this sketch terminates!!! // There should be a way to schedule a method when Processing closes this // sketch, so we can close the device there, but it is not documented for Processing 3. receiver = device.getReceiver(); // NOTE: Either of the above method calls can throw MidiUnavailableException // if there is no available device or if it does not have a Receiver to // which we can send messages. The catch clause intercepts those error messages. // See https://www.midi.org/specifications/item/gm-level-1-sound-set, use voice variable ShortMessage noteMessage = new ShortMessage() ; noteMessage.setMessage(ShortMessage.PROGRAM_CHANGE, 0, voice, 0); // to channel 0 receiver.send(noteMessage, -1L); // send it now } catch (MidiUnavailableException mx) { System.err.println("MIDI UNAVAILABLE"); // Error messages go here. device = null ; receiver = null ; // Do not try to use them. } catch (InvalidMidiDataException dx) { System.err.println("MIDI ERROR: " + dx.getMessage()); // Error messages go here. } } void draw() { background(0,255,255); fill(200,0,0); stroke(0); float horizontalOffset = (mouseX - width/2.0) / (width/2.0); // interval [-1.0,1.0] float veryticalOffset = (mouseY - height/2.0) / (height/2.0); // interval [-1.0,1.0] float diameter = width / (float) numLocations ; // used in fractional calculations int intdiameter = width / numLocations ; // used in integer increments // DOING THESE REPETITIVE CALCS IN THE LOOP WAS SLOWING THINGS DOWN, SO PRECOMPUTE THEM HERE. float horizo = horizontalOffset * diameter / (nestCircles * 2.0) ; float vertico = veryticalOffset * diameter / (nestCircles * 2.0); float floatCircles = nestCircles ; strokeWeight(strokew8); for (int x = 0 ; x <= width ; x += intdiameter) { for (int y = 0 ; y <= height ; y += intdiameter) { // the height & width of a circle must be the same pushMatrix(); // nest circles at this location, pop for the next location translate(x,y); // STUDENT REQ 1: MOVE THE "c" LOOP FROM BELOW TO *BEFORE* ENTERING THE x,y NESTED LOOPS. // CREATE A GROUP PShape "bigShape" BEFORE ENTERING THE "c" LOOP. REPLACE THE CODE INSIDE THE "c" // LOOP AS FOLLOWS. Create a PShape object "PShape littleShape" of type ELLIPSE, // THEN SCALE littleShape (use littleShape.scale()), then translate littleShape, using // arguments from below for scale and translate. Then add littleShape to the GROUP PShape // using addChild(), before looping back up to the top of the "c" loop. // The new "c" loop will build a GROUP PShape object "bigShape" containing the nested circles. // Notice that the order of the calls to translate and scale must be reversed when // building a PShape object with the P2D or P3D (OpenGL) renderer. // FINALLY, replace the entire, original "c" loop below with a call to display // your GROUP PShape. After making this change, your sketch should run much faster. // You do NOT need pushMatrix()...popMatrix() INISIDE your new "c" loop, because // you are building a PShape(GROUP) "off to the side", not plotting it in the "c" loop. // Keep the outer push-pop pair in the for x,y nested loop. // REFERENCE PAGES: createShape and PShape. USE THESE FIRST 2!!! // https://processing.org/reference/createShape_.html // https://processing.org/reference/PShape.html (see methods at bottom). // There is a too-big-for this assignment tutorial on PShape here: // https://processing.org/tutorials/pshape/ // Notes on the order-reversal of geometric transforms for OpenGL PShapes: // https://github.com/processing/processing/issues/2783 // TEST FOR SPEED WHEN DONE WITH REQ 1. for (int c = 0 ; c < nestCircles ; c++) { pushMatrix(); translate((c+1) * horizo, (c+1) * vertico); scale((nestCircles - c) / floatCircles); // next nested one is smaller by 1/nth ellipse(0,0,diameter,diameter); popMatrix(); } popMatrix(); } } sendMIDI(); // every 10th of a second is a lot! } // See http://midi.teragonaudio.com/tech/midispec.htm // and https://www.midi.org/specifications/item/gm-level-1-sound-set // See Parson's code at http://faculty.kutztown.edu/parson/spring2017/MidiKBSpring2016Parson.txt void sendMIDI() { // STUDENT REQ 2: Implement sendMIDI as follows. // See code in setup() above and in my MIDI example sketches linked on the course page at // http://faculty.kutztown.edu/parson/spring2017/MidiKBSpring2016Parson.txt // http://faculty.kutztown.edu/parson/spring2017/DemoMidiSpring2016.txt // to see how to send NOTE_ON and NOTE_OFF message to the selected MIDI synth. // PSEUDO-CODE FOR THIS FUNCTION: // 1. Construct a MIDI ShortMessage object to hold data to send to the MIDI synth. // 2a. If the mouse is not pressed: // If lastnote shows that a note was previously sent (lastnote is not -1): // Send a NOTE_OFF message for lastnote to the synth. // Reset lastnote and lastvolume to -1. // 2b. Else (mouse is currently pressed) // Set a local variable note to a value from 0 to 127 inclusive, where note must // be 0 is mouseX is 0, 127 if mouseX is width, and in-between for other mouseX locations. // In other words, the contrained ratio (see constrain) of mouseX to width times 127 // gives te note. // Similarly, set a local variable volume to a value of 0..127, with 0 for the // bottom of the display (height), 127 for the top (mopuseY of 0), otherwise // linearly in between. You can just subtract the contrained ratio (see constrain) // of mouseY to height times 127 from 127 to get the volume. // If lastnote shows that a note was previously sent (lastnote is not -1): // Send a NOTE_OFF message for lastnote to the synth. // Reset lastnote and lastvolume to -1. // Record the new note and volume in global variables lastnote and lastvolume. // Send the new note and volume to the synth. // You MUST wrap your code within this function in a try-catch block for exception // type InvalidMidiDataException in case you have bad data. // Just duplicate my "catch (InvalidMidiDataException dx)" code in setup() above. // A STUCK NOTE INCURS A 5 POINT PENALTY. } // STUDENT REQ 3: Add code for RIGHT arrow to increment the "voice" variable // that gives the instrument voice, and code for LEFT to decrement it. // If lastnote shows that a note was previously sent (lastnote is not -1): // Send a NOTE_OFF message for lastnote to the synth. // Reset lastnote and lastvolume to -1 // Make sure "voice" wraps from 127 to 0 on RIGHT, and 0 to 127 on left. // For either of these two changes to voice, send the PROGRAM_CHANGE message // to the synth, see the setup() function above for an example. void keyPressed() { if (key == CODED) { if (keyCode == UP) { if (nestCircles < limitNestCircles) { nestCircles++; } } else if (keyCode == DOWN) { if (nestCircles > 1) { nestCircles--; } } } } // STUDENT REQ 4: Implement the distance function to return // the distance from point x1,y1 to point x2,y2, where distance // is the square root of the sum of the squares of the differences // in the x and y coordinates, i.e., // SQUAREROOT(SQUARE(x1-x2) + SQUARE(y1-y2)) // Use the library sqrt() and sq() functions. float distance(int x1, int y1, int x2, int y2) { // Delete the next line, then add your code. return 0 ; } // STUDENT REQ 5: Change the color of the GROUP PShape where it is plotted // up in draw() to blue when the mouse is pressed AND the mouse is within 1 diameter // away from the center of the current GROUP PShape being plotted, otherwise make it red. // You can pick your own 2 colors, but they must be distinguishable. // Use the bigShape.setFill(color(R,G,B)) method on the GROUP PShape // object bigShape (or whatever you call it). // STUDENT REQ 6 IN THE "c" loop: Use RECT PShapes when voice is an even number, // ELLIPSE PShapes when it is odd, instead of sticking to ELLIPSE. I moved // the "c" loop out into a new helper function to do this part: // PShape makeShape(float diameter, float floatCircles, float horizo, float vertico) // and placed a call to that from draw(). It is up to you whether to do that. // Here is my code to check odd vs. even; iscirc is true on an odd voice number. // boolean iscirc = ((voice & 1) == 1) ; // Use bitwise AND to look at bottom bit. // STUDENT REQ 7: At the bottom of the draw() function display the text // "Patch: N" where N is the voice number near the bottom left of the display, // in a color that is clear to see. I used black.