// 5/21/2005 // ----------------------------------------------- // Members // ----------------------------------------------- // We'll play this pattern in strict order, repeatedly. Cue[] pattern = null; // The number of beats in the pattern. float patternLength = 4; // The tempo is counted in beats per minute. float tempo = 30.0; // This member helps us track the last Cue played. It refers to the array index of the pattern array // containing the next Cue to be played. int patternIndex; // Since time elapses between the applet loading and draw begin called, we're going to adjust the start // time... int realStartTime = 0; // ----------------------------------------------- // Contants // ----------------------------------------------- int MARGIN_LEFT = 25; int MARGIN_RIGHT = 25; int MARGIN_TOP = 50; int MARGIN_BOTTOM = 40; // ----------------------------------------------- // Methods // ----------------------------------------------- /* * The setup method */ void setup() { size(400, 150); PSound sound1 = loadSound("test1.wav"); PSound sound2 = loadSound("test2.wav"); pattern = new Cue[8]; pattern[0] = new Cue(sound1, 0); pattern[1] = new Cue(sound2, 0.5); pattern[2] = new Cue(sound1, 1); pattern[3] = new Cue(sound2, 1.5); pattern[4] = new Cue(sound1, 2); pattern[5] = new Cue(sound2, 2.5); pattern[6] = new Cue(sound1, 3); pattern[7] = new Cue(sound2, 3.5); // Initialize to the start of the pattern. patternIndex = 0; textFont(loadFont("Copperplate-Light-24.vlw")); // framerate(64); realStartTime = millis(); // println("realStartTime == " + realStartTime); } void draw() { float currentBeat = getCurrentBeat(); // println("currentBeat == " + currentBeat ); float nextCueTime = pattern[patternIndex].getCueTime(); // println("nextCueTime == " + nextCueTime); // It seems like there should be a better way than ensuring that we hit the beat within a // certain time frame... if( currentBeat >= nextCueTime && (currentBeat - nextCueTime) < 0.1) { PSound theSound = pattern[patternIndex].getSound(); theSound.stop(); theSound.play(); patternIndex = (patternIndex + 1) % pattern.length; // println( "playing #" + patternIndex ); } // The next section deals with drawing the display // This bit draws a rectangle inside our margins. background(#FEFEFE); noFill(); stroke(0, 0, 0); strokeWeight(2); rectMode(CORNERS); rect( MARGIN_LEFT, MARGIN_TOP, width - MARGIN_RIGHT, height - MARGIN_BOTTOM ); // Here we draw rectangles representing the sounds stroke( 224, 224, 64 ); fill( 224, 224, 64, 128 ); strokeWeight(1); rectMode(CORNER); for(int i=0; i < pattern.length; i++ ) { float startingBeat = pattern[i].getCueTime(); float durationSeconds = pattern[i].getSound().duration(); float durationBeats = secondsToBeats(durationSeconds); float leftX = MARGIN_LEFT + ((width - MARGIN_LEFT - MARGIN_RIGHT) * (startingBeat/patternLength)); // println( "leftX == " + leftX); float rectWidth = ((width - MARGIN_LEFT - MARGIN_RIGHT) * (durationBeats/patternLength)); // println( "rectWidth == " + rectWidth ); rect( leftX, MARGIN_TOP, rectWidth, height - MARGIN_BOTTOM - MARGIN_TOP ); } float progress = currentBeat/patternLength; float centerX = MARGIN_LEFT + ((width - MARGIN_LEFT - MARGIN_RIGHT) * progress); stroke( 64, 64, 224 ); fill( 64, 64, 224, 128 ); strokeWeight(1); rectMode(CENTER); rect( centerX, height/2, 10, height ); // Finally draw the framerate drawFrameRate(); } // This would eventually be a method of the pattern object because it accounts for the pattern // length. It also checks the current time, so perhaps it should be parameterized... float getCurrentBeat() { // Minutes since start over tempo (in BPM) modulo number of beats in the pattern. // println("millis() == " + millis()); // println("(millis()/1000.0/60.0)*tempo == " + (millis()/1000.0/60.0)*tempo); int offset = millis() - realStartTime; return secondsToBeats(offset/1000.0) % patternLength; } /* * Converts a number of seconds to a number of beats, based on tempo. */ float secondsToBeats(float seconds) { return (seconds/60) * tempo; } /* * */ void drawFrameRate() { frameCounter++; if( frameCounter > 100 ) { lastFrameRate = framerate; frameCounter = 0; } String strFramerate = nf(round(lastFrameRate), 3); float fpsOffset = textWidth(strFramerate); fill(#000000); text( strFramerate, width - MARGIN_RIGHT - fpsOffset, height - 12 ); } int frameCounter = 0; float lastFrameRate = 0; // ----------------------------------------------- // Inner Classes // ----------------------------------------------- /* * The Cue class encapsulates a time for the sound to be played and a reference to the sound itself. */ class Cue { /* * Creates a new Cue. * param aSound See getSound(). * param aCueTime See getCueTime(). */ Cue( PSound aSound, float aCueTime ) { this.sound = aSound; this.cueTime = aCueTime; } /* * See getSound(). */ private PSound sound; /* * See getCueTime() */ private float cueTime; /* * The cueTime indicates the number of beats into the parent pattern that the cue should be executed * e.g. the sound should be played. A measure may be counted verbally as "one and-a two and-a three * and-a four". If the cueTime value is 0, then the sound will be played on "one". If the cueTime * value is 1, then the sound will be played one beat into the measure, i.e. on the count of "two". * Setting the cueTime to 1.5 will cause the sound to be played on the off beat between one and two, * i.e. on the "and-a". */ float getCueTime() { return cueTime; } /* * This sound will be played when we reach the cue's alloted time. */ PSound getSound() { return sound; } }