// Class Note manages the display and NOTE_ON/NOTE_OFF of a constant-pitch, // constant-velocity note on the visual "keyboard". class Note { int pitch ; // first argument for NOTE_ON and NOTE OFF int velocity ; // second argument for NOTE_ON and NOTE OFF int x, y ; // x,y coordinates relative to 0,0 in center of display int wide, high ; // width, height of this shape int Hue, Sat, Brt ; // Hue, Saturation, Brightness of this Note object. int musicalScaleStep ; float velix, volLevels ; final PShape shp ; int nesting = 0 ; int nestSpeed = 1 ; final int nestLimit = 10 ; final int nestDelayLimit = 1 ; int nestDelay = nestDelayLimit ; Map isSounding = new HashMap(); // Map channel to whether it is sounding or not on each channel, initially not. int noteson = 0 ; // across all channels Note(int pitch, int velocity, int x, int y, int wide, int high, int Hue, int Sat, int Brt, int musicalScaleStep, int velix, int volLevels) { // Copy parameters into data fields. this.pitch = pitch ; this.velocity = velocity ; this.x = x ; // Prefix class data field with "this." when function parameter or this.y = y ; // local variable has the same variable name. "this." is this object. this.wide = wide ; this.high = high ; this.Hue = Hue ; this.Sat = Sat ; this.Brt = Brt ; this.musicalScaleStep = musicalScaleStep ; this.velix = velix ; this.volLevels = volLevels ; shp = SVGshapes[musicalScaleStep % SVGshapes.length]; for (int c = 0 ; c < 16 ; c++) { // There are up to 16 channels (instruments) in MIDI. isSounding.put(c, false); // Each channel is NOT sounding at the start. } } // Display the Note, NOTE_ON when isBeingPressed on channel, NOTE_OFF when not. void display(boolean isHighlighted, boolean isBeingPressed, int channel, boolean isOSCmessage) { pushMatrix(); pushStyle(); translate(x, y); try { if (isHighlighted || isBeingPressed || isSounding.get(channel)) { fill(Hue, 99, 99); tint(Hue, 99, 99); if (isBeingPressed) { stroke(0, 0, 0); } else { stroke(Hue,99,99); } } else { fill(Hue, 99, 99 /* Brt */); // Brt is dimmer for lower-velocity notes. tint(Hue, 99, 99 /*Brt*/); stroke(Hue,99,99); } // rect(0, 0, wide, high); float scaler = ((127-velocity) / 64.0) + 3; // float scalerBIG = scaler * 10.0 ; image(hexagon, 0, 0, wide * scaler, high * scaler); int minny = min(width, height) ; if (isBeingPressed || isSounding.get(channel) || noteson > 0) { pushMatrix(); shp.setStrokeWeight(1); shp.setFill(color(Hue, 99, 99, 2)); // Draw nested translucent shapes, full screen, at the center. for (float sc = 1.0 ; sc > 0.1 ; sc -= 0.25) { shape(shp, -x, -y, minny*sc, minny*sc); } stroke(0,0,0); // black fill(Hue, 99, 99); tint(Hue, 99, 99); float tallness ; if (velix == 0) { tallness = 100.0 ; } else if (velix == 1) { tallness = 550.0 ; } else { tallness = 875.0 ; } noStroke(); float xoffset = wide * scaler * (random(-0.1, 0.1)); float yoffset = high * scaler * (random(-0.1, 0.1)); int tr = 1 ; float nestScaleHeight = (nesting+1.0) / nestLimit ; LinkedList TR = new LinkedList (); for (float sc = 2.0 ; sc <= tallness * nestScaleHeight ; sc += 20.0, tr += 1) { if (tr > 15) { tr = 15 ; } TR.add(tr); } //int lasttr = 0 ; // PShape shp = SVGshapes[musicalScaleStep % SVGshapes.length]; //shp.setStroke(color(0,0,0)); shp.setStrokeWeight(1); shp.setFill(color(Hue, 99, 99)); while (TR.size() > 0) { int trelem = TR.pollFirst(); //lasttr = trelem ; translate(0, 0, trelem); // cumulative increases in Z // println("TR " + trelem); ; shape(shp, 0 + xoffset, 0 + yoffset, wide * scaler, high * scaler); // scale(1.02, 1.02, 1); // Experiment - make bigger as they go up. } //translate(0, 0, lasttr); // Nest the topmost one. shp.setStrokeWeight(1); for (int nst = 0 ; nst < nesting ; nst++) { scale(.85); shape(shp, 0 + xoffset, 0 + yoffset, wide * scaler, high * scaler); } nestDelay-- ; if (nestDelay <= 0) { nestDelay = nestDelayLimit ; nesting += nestSpeed ; if (nesting > nestLimit || nesting == 0) { nestSpeed = - nestSpeed ; } } popMatrix(); if (! isSounding.get(channel)) { ShortMessage newMessage = new ShortMessage() ; int v = (killfade < 0) ? velocity : round(killfade * velocity); newMessage.setMessage(ShortMessage.NOTE_ON, channel, pitch, v); receiver.send(newMessage, -1L); // send it now // if (isOSCmessage) { isSounding.put(channel, true); // } noteson++ ; } } if (isSounding.get(channel) && ! isBeingPressed) { ShortMessage newMessage = new ShortMessage() ; newMessage.setMessage(ShortMessage.NOTE_OFF, channel, pitch, velocity); receiver.send(newMessage, -1L); // send it now isSounding.put(channel, false); noteson-- ; } } catch (InvalidMidiDataException dx) { System.err.println("MIDI ERROR: " + dx.getMessage()); // Error messages go here. } popStyle(); popMatrix(); } void setShearXSpeed(float speedInDegrees) { } // We will need this only if we get to moving Note objects in assn4. // Note that my rotateBB function is available if we do 2D rotation. int [] get2DBoundingBox() { int [] result = new int [4]; result[0] = x - wide/2 ; result[1] = y - high/2 ; result[2] = x + wide/2 ; result[3] = y + high/2 ; return result; } int getX() { return x ; } int getY() { return y ; } }