Kevin Riordan and Sharon Yang- Final Project Section C

When many keys are pressed
kzr junginny
When one key is held down
When mouse is hovered over control display

final project code

/*************************************************************************************************************************************************************************************
Kevin Riordan and Sharon Yang 15-104 Final Project Section C kzr@andrew.cmu.edu, junginny@andrew.cmu.edu
--*-IMPORTANT NOTE-*-------------------------------------
Though this final project is a collaboration between the two of us, we both wrote the code entirely together on my computer, because everything fits like a puzzle with everything
else and having it divided up among us would have made the code much less efficient (at least in our opinion). So, after meeting with Prof. Dannenberg, he agreed that as long
as this project is thoroughly documented, we can both be graded on the project in it's entirety.
--*-VISUAL DESCRIPTION-*---------------------------------
This project uses keyboard input to press the corresponding keys, which are shown when hovering over the area displayed in the top right corner of the canvas. When the key is
pressed, the corresponding note is played, a note with a color corresponding to the key and varying properties including fadeout rate, vertical speed, and note tail orientation
is created. The amount of notes created depends on how long the key is pressed down for. Lights on top of the speakers on each side of the stage and at the top display the
corresponding color, ending at the corresponding dancer location. The dancer at that location also cycles through its animation, and remembers what frame it was on and pauses when
the key is not pressed. There are four different types of dances, based on key position. Grey flickering lights, somewhat resembling a glowing disco ball (which is why its 
called discoLighting in the code) also randomly flicker both above the speakers and at the top behind the lights when a key is pressed.
--*-IMPROVEMENTS TO STRUCTURE/ FUTURE IMPLEMENTATIONS-*--
1-- A Dictionary called keyToColorHex was implemented using Hexcodes for colors near the start for the colors of each corresponding note, but later on in the project we wanted
    to vary the fadeout rate of the notes, and using fill(hexcode,alpha) is not possible, so we had to write a basic hexcode to r,g,b converter found between lines ~100 - ~120.
    These lines are unneccessary if the dictionary is changed to be in a similar format as the discoLighting array, with each key taking an r,g,b entry instead.
2-- THE SOUNDS LOOP WHEN THE KEY IS PRESSED, INSTEAD OF JUST PLAYING ONCE NO MATTER HOW LONG THE KEY IS HELD DOWN FOR ****THIS HAS BEEN FIXED AS OF 12-4-2018****
3-- The sound when the piano key is pressed should have the duration change up to a certain length while the key is held down, then fade out like a piano does in the real world
    However, when lengthening the sound using p5 sound commands, the pitch decreases (as the reference sheet warns), so a more complex solution would be required. Because the 
    main focus of this project shifted from the piano to the visual display as it was created, we feel this is not an urgent change, though it would improve the overall quality
    of the project.
--*-SOURCES FOR SOUNDS AND DANCE ANIMATIONS-*------------
This project uses free sprite sheets available for download at https://www.deviantart.com/gladkirby7/art/Vexy-Dances-FruityDance-Sprite-Sheet-675963897 for the arm dance and
windmill & https://www.pinterest.com/pin/525021269044190160/?lp=true for the spin dance && https://blueprintinteractive.com/sites/default/files/images/carlton_sprites2.png
for the man dance. The sounds for each piano key were downloaded from https://freesound.org/people/jobro/packs/2489/.
************************************************************************************************************************************************************************************/
// Variables corresponding to notes Example: cO1 = Note C in Octave 1
var cO1, dO1, eO1, fO1, gO1, aO1, bO1, cSO1, dSO1, fSO1, gSO1, aSO1, cO2, dO2, eO2, fO2, gO2, aO2, bO2, cSO2, dSO2, fSO2, gSO2, aSO2;
// Array that keeps track of total presses for each key Example: cO1 is index position 0, stores X amount of clicks
var numberOfPresses = [];
// Arrays used to store animation frames for their specific dance
var windMillDance = [];
var armDance = [];
var manDance = [];
var spinDance = [];
// Array used to store what the dancers can do Example: what dance it does, what frame of the dance it is on, if it is dancing or not
var peopleArray = [];
// Array used to store the specific property within the people Array Example: 0 means it is on the first frame, 7 means it is on the last frame
var whatFrameIsItArray = [];
// Array to store the notes with their specific properties when a key is pressed
var notesArray = [];
// Dictionary to store whether a key is already pressed down when the keys functions are called again.
var wasPressed = {'Q': false, '2': false, 'W': false, '3': false, 'E': false, 'R': false, '5': false, 'T': false, '6': false, 'Y': false, '7': false, 'U': false, 'Z': false, 'S': false, 'X': false, 'D': false, 'C': false, 'V': false, 'G': false, 'B': false, 'H': false, 'N': false, 'J': false, 'M': false};
// Variables to determine width and height of keys
var whiteBottomHeight = 15;
var wKeyW = 40;
var wKeyH = 190;
var blackBottomHeight = 10;
var bKeyW = 30;
var bKeyH = 135;
// Dictionaries used to convert keys to the corresponding information, documented in more detail when they are called in specific functions below
var keyToColorHex = {'Q': '#D8BFD8', '2': '#3C8696', 'W': '#2DD652', '3': '#B632A3', 'E': '#F9EF15', 'R': '#8A36F6', '5': '#D64939', 'T': '#2DEC74', '6': '#B08FD2', 'Y': '#25BFEC', '7': '#EC838D', 'U': '#534EEE', 'Z': '#B41E3C', 'S': '#ECC25F', 'X': '#1DB071', 'D': '#7F75F0', 'C': '#EE853E', 'V': '#CFE397', 'G': '#6EEEDE', 'B': '#EE6EC8', 'H': '#FB5441', 'N': '#7B90D8', 'J': '#EA1D32', 'M': '#91F473'}; 
var whiteKeyToX = {'Q': 20, 'W': 60, 'E': 100, 'R': 140, 'T': 180, 'Y': 220, 'U': 260, 'Z': 300, 'X': 340, 'C': 380, 'V': 420, 'B': 460, 'N': 500, 'M': 540};
var blackKeyToX = {'2': 45, '3': 85, '5': 165, '6': 205, '7': 245, 'S': 325, 'D': 365, 'G': 445, 'H': 485, 'J': 525};
var keyItoClicks = {'Q': 0, '2': 1, 'W': 2, '3': 3, 'E': 4, 'R': 5, '5': 6, 'T': 7, '6': 8, 'Y': 9, '7': 10, 'U': 11, 'Z': 12, 'S': 13, 'X': 14, 'D': 15, 'C': 16, 'V': 17, 'G': 18, 'B': 19, 'H': 20, 'N': 21, 'J': 22, 'M': 23};
// Array used for the respective shapes below and behind the lights to flicker "randomly" (in fact it is just shuffled) when the lights flicker, which make the lights prettier
var discoLighting = [(220,220,220),(211,211,211),(192,192,192),(169,169,169),(128,128,128),(105,105,105),(119,136,153),(112,128,144),(47,79,79),(47,79,79)];
// Preloading in all the sounds and animations for the dances, outputting to console when each respective group of files are ready to play
function preload() {
    cO1 = loadSound("cKeyOctave1.wav");
    dO1 = loadSound("dKeyOctave1.wav");
    eO1 = loadSound("eKeyOctave1.wav");
    fO1 = loadSound("fKeyOctave1.wav");
    gO1 = loadSound("gKeyOctave1.wav");
    aO1 = loadSound("aKeyOctave1.wav");
    bO1 = loadSound("bKeyOctave1.wav");
    console.log("Loaded White Keys Octave 1");
    cSO1 = loadSound("cSharpOctave1.wav");
    dSO1 = loadSound("dSharpOctave1.wav");
    fSO1 = loadSound("fSharpOctave1.wav");
    gSO1 = loadSound("gSharpOctave1.wav");
    aSO1 = loadSound("aSharpOctave1.wav");
    console.log("Loaded Black Keys Octave 1");
    cO2 = loadSound("cKeyOctave2.wav");
    dO2 = loadSound("dKeyOctave2.wav");
    eO2 = loadSound("eKeyOctave2.wav");
    fO2 = loadSound("fKeyOctave2.wav");
    gO2 = loadSound("gKeyOctave2.wav");
    aO2 = loadSound("aKeyOctave2.wav");
    bO2 = loadSound("bKeyOctave2.wav");
    console.log("Loaded White Keys Octave 2");
    cSO2 = loadSound("cSharpOctave2.wav");
    dSO2 = loadSound("dSharpOctave2.wav");
    fSO2 = loadSound("fSharpOctave2.wav");
    gSO2 = loadSound("gSharpOctave2.wav");
    aSO2 = loadSound("aSharpOctave2.wav");
    console.log("Loaded Black Keys Octave 2");
    for (var i = 0; i < 8; i++) {
        windMillDance[i] = loadImage("assets/windmill" + i + ".png");
        armDance[i] = loadImage("assets/Arm" + i + ".png");
        manDance[i] = loadImage("assets/man" + i +".png");
        spinDance[i] = loadImage("assets/spingirl" + i +".png")
    }
    console.log("Loaded Dance Animations");
}
/*************************************************************************************************************************************************************************************
Below are the functions used for this project. Documenting these is somewhat hard because many functions use parts of other functions, in addition to many arrays / dictionaries
*************************************************************************************************************************************************************************************/
// These functions remove the hashtag from the color hexcode, then converts the remaining hexcode into a base 16 number, from which the r,g,b values can be extracted based on
// their respective positions within the resulting number.
// This function converts the hexcode into the r value.
function hexToR(hex) {
    hex = hex.replace(/[^0-9A-F]/gi, '');
    var hexConvert = parseInt(hex,16);
    var r = (hexConvert >> 16) & 255;
    return r;
}
// This function converts the hexcode into the g value.
function hexToG(hex) {
    hex = hex.replace(/[^0-9A-F]/gi, '');
    var hexConvert = parseInt(hex,16);
    var g = (hexConvert >> 8) & 255;
    return g;
}
// This function converts the hexcode into the b value.
function hexToB(hex) {
    hex = hex.replace(/[^0-9A-F]/gi, '');
    var hexConvert = parseInt(hex,16);
    var b = hexConvert & 255;
    return b;
}
// This function's overall purpose is to take in properties it needs from various other parts, then draws a light from the top of the screen down to the corresponding character
// using the x property. It also draws the light in the color corresponding to the key pressed, coming from the colorHex, key, and whatDance properties. Another group of lights
// are also drawn above the speakers in the same color as the overhead lights.
function makeLight(x,colorHex,key,whatDance) {
    stroke(hexToR(keyToColorHex[key]),hexToG(keyToColorHex[key]),hexToB(keyToColorHex[key]),random(80,120));
    strokeWeight(1);
    fill(hexToR(keyToColorHex[key]),hexToG(keyToColorHex[key]),hexToB(keyToColorHex[key]),random(80,120));
//  This next section of code draws the overhead lights
    beginShape();
    var mappedX = map(x,20,540,220,382);
    vertex(mappedX - 2.5,50 + random(-2.5,2.5));
    vertex(mappedX + 2.5,50);
//  The if statements determine the end location of the light, based on what position the dancer is in and what dance it is doing.
    if (peopleArray[whatDance].framesArray == spinDance) {
        var endX = (peopleArray[whatDance].x - 25) * 0.85 + 70;
        var endY = peopleArray[whatDance].y + 20;
        var sizeBeam = 1;
    }
    else if (peopleArray[whatDance].framesArray == armDance) {
        var endX = peopleArray[whatDance].x * 0.7 + 120;
        var endY = peopleArray[whatDance].y - 50;
        var sizeBeam = 0.75;
    }
    else if (peopleArray[whatDance].framesArray == manDance) {
        var endX = peopleArray[whatDance].x * 0.5 + 165;
        var endY = peopleArray[whatDance].y - 90;
        var sizeBeam = 0.5;
    }
    else if (peopleArray[whatDance].framesArray == windMillDance) {
        var endX = peopleArray[whatDance].x * 1.1 + 50;
        var endY = peopleArray[whatDance].y + 100;
        var sizeBeam = 1.3;
    }
    vertex(endX + (25 * sizeBeam), endY);
    vertex(endX - (25 * sizeBeam), endY)
    endShape();
//  This for loop draws the lights in the same color as the overhead light on the tops of the side speakers
    for (var i = 0; i < 5; i++) {
        triangle(20 * i,197,(20 * i) - 13,0,(20 * i) + 15,0);
        triangle(20 * i + 15,194,(20 * i + 15) - 3,0,(20 * i + 15) + 8,0);
        triangle(20 * i + 510,197,(20 * i + 510) - 9,0,(20 * i + 510) + 15,0);
        triangle(20 * i + 495,194,(20 * i + 495) - 7,0,(20 * i + 495) + 13,0);
    }
    noStroke();
}
// This function loads people into an array of size 24, corresponding to the number of keys on the piano. It also loads in the animation frame array, setting each of the dancers
// to the first frame of their 8-frame animation.
function loadAnimationArray() {
    for (var i = 0; i < 24; i++) {
        peopleArray[i] = makePerson(22 * i + 5);
        whatFrameIsItArray[i] = 0;
    }
}
// This function is what creates and returns all the variables needed for the person to know what it needs to. This is the object that is loaded into the peopleArray.
function makePerson(x) {
    var person = {x: x, y: 245 + random(-3,3), framesArray: windMillDance, whatFrameIsIt: whatFrameIsItArray, startDancing: startDancing};
    return person;
}
// This function sets the dances for people in the array. When the array is first made, the frameArray property is set at a default windMillDance as shown above.
function setDancesForPeople() {
//  This for loop iterates through the length of the peopleArray to make sure every dancer's dance is changed accordingly.
    for (var i = 0; i < 24; i++) {
//      These if statements load in the corresponding dance based on the modulus operator Example: every fourth dancer does the spin dance.
        if (i % 4 == 0) {
            peopleArray[i].framesArray = windMillDance;
        }
        else if (i % 4 == 1) {
            peopleArray[i].framesArray = armDance;
        }
        else if (i % 4 == 2) {
            peopleArray[i].framesArray = manDance;
        }
        else if (i % 4 == 3) {
            peopleArray[i].framesArray = spinDance;
        }
    }
}
// This function is called startDancing, because it is the function that actually makes the image on the canvas, but the main purpose is to resize the images and put them into
// their corresponding place based on their dance, because each image was a different size initially.
function startDancing(whatFrame) {
    push();
//  These if statements are what resize the image as described above, and move it to their location based on the x and y properties (an additional hardcoded shift had to be 
//  implemented because of the rescaling of the image).
    if (this.framesArray == manDance) {
        translate(this.x * 0.45 + 160,this.y - 135);
        scale(0.25,0.25);
    }
    else if (this.framesArray == spinDance) {
        translate((this.x - 25) * 0.8 + 45,this.y - 60);
        scale(0.7,0.7);
    }
    else if (this.framesArray == windMillDance) {
        translate(this.x + 25,this.y);
        scale(0.3,0.2);
    }
    else if (this.framesArray == armDance) {
        translate(this.x * 0.7 + 100,this.y - 100);
        scale(0.15,0.1);
    }
    image(this.framesArray[whatFrameIsItArray[whatFrame]], 0, 0);
    pop();
}
// This function updates the dancers to the next frame of their animation, and also updates the numberOfPresses Array.
function checkAndUpdateDancing() {
//  This for loop calls the start dance function, shown above.
    for (var i = 0; i < 24; i++) {
        peopleArray[i].startDancing(i);
//      This if statement increases the number of presses every 5 "actual" presses, because I found the refresh rate to be too fast. Increasing the 5 will slow down the animation
//      Example: changing the 5 to a 10 will approximately halve the animation speed.
        if (numberOfPresses[i] % 5 == 0) {
            numberOfPresses[i]++;
            whatFrameIsItArray[i]++;
//          This if statement ensures the animations smoothly loop, and dont return undefined because each animation is only 8 frames long.
            if (whatFrameIsItArray[i] > 7) {
                whatFrameIsItArray[i] = 0;
            }
        }
    }
}
// This function is what creates and returns a note with all of the properties it needs to know.
function makeNote(x, colorHex) {
    var note = {x: random(5,550), y: 285 + random(-25,25), colorHex: colorHex, size: random(0.2,1), lineFactor: random(1.5,3.5), willItBeRotated: random(0,2), howFastItGoesUp: random(0.7,1.4), opacity: 255, fadeOutChange: random(0.4,3)};
    return note;
}
// This function is what actually displays the notes on the canvas, as well as updates existing notes in the notesArray based on their respective properties, and also updates the
// property that determines how fast it fades out.
function displayNotes() {
//  This function iterates through the notesArray.
    for (var i = 0; i < notesArray.length; i++) {
        strokeWeight(notesArray[i].size * 3.5);
        stroke(hexToR(notesArray[i].colorHex),hexToG(notesArray[i].colorHex),hexToB(notesArray[i].colorHex),notesArray[i].opacity);
        fill(hexToR(notesArray[i].colorHex),hexToG(notesArray[i].colorHex),hexToB(notesArray[i].colorHex),notesArray[i].opacity);
        ellipse(notesArray[i].x,notesArray[i].y,notesArray[i].size * 20,notesArray[i].size * 15);
//      This if statement determines if the note tail will be up or down.
        if (notesArray[i].willItBeRotated < 1) {
            line(notesArray[i].x + (notesArray[i].size * 10),notesArray[i].y,notesArray[i].x + (notesArray[i].size * 10),notesArray[i].y - (notesArray[i].size * 20 * notesArray[i].lineFactor));
            line(notesArray[i].x + (notesArray[i].size * 10),notesArray[i].y - (notesArray[i].size * 20 * notesArray[i].lineFactor),notesArray[i].x + (notesArray[i].size * 15),notesArray[i].y - (notesArray[i].size * 20 * notesArray[i].lineFactor) + (notesArray[i].lineFactor * notesArray[i].size * 10));
        } else {
            line(notesArray[i].x - (notesArray[i].size * 10),notesArray[i].y,notesArray[i].x - (notesArray[i].size * 10),notesArray[i].y + (notesArray[i].size * 20 * notesArray[i].lineFactor));
            line(notesArray[i].x - (notesArray[i].size * 10),notesArray[i].y + (notesArray[i].size * 20 * notesArray[i].lineFactor),notesArray[i].x - (notesArray[i].size * 15),notesArray[i].y + (notesArray[i].size * 20 * notesArray[i].lineFactor) - (notesArray[i].lineFactor * notesArray[i].size * 10));
        }
        notesArray[i].opacity -= notesArray[i].fadeOutChange;
    }
    strokeWeight(1);
}
// This function goes through the notesArray, which stores all the note objects and updates the y coordinate of each note object based on its howFastItGoesUp property. Thus, every
// note has a different speed at which it ascends, leading to a more pleasing visual.
function checkAndUpdateNotes() {
//  This for loop iterates through the notesArray.
    for (var i = 0; i < notesArray.length; i++) {
//      This if statement checks whether the y coordinate of each note needs to be updated.
        if (notesArray[i].y != 0) {
            notesArray[i].y -= notesArray[i].howFastItGoesUp;
        }
    }
}
// This function removes notes that have gone offscreen.
function removeOffScreenNotes() {
//  This for loop iterates through the notesArray.
    for (var i = 0; i < notesArray.length; i++) {
//      This if statement checks whether the vertical coordinate is less than or equal to 0, and if it is, it removes the note from the array. I thought about putting this together
//      with the above function, but if I did that, when the array length changes because of the resulting shift, it might cause some minor errors in the code, so it is safer to 
//      split them up.
        if (notesArray[i].y <= 0) {
            notesArray.shift(notesArray[i]);
        }
    }
}
// This function determines whether the note will come out when the key is pressed (keys are very sensitive and log presses rapidly when the key is held down).
function noteComesOut() {
    var probability = random(0,1);
//  Changing the value in this if statement will change the amount of notes on screen, if you want less clutter.
    if (probability >= 0.5) {
        return true;
    } else {
        return false;
    }
}
// This function is what displays the piano and the steps as well as the speakers at the tops and sides of the canvas. 
function pianoStructureAndLights() {
//  Creating the rectangles that make up the piano.
    rectMode(CORNER);
    noStroke();
    stroke('#8B4513');
    fill('#3f2a14');
    rect(0,397,600,205);
    noStroke();
    fill('#52361b');
    rect(0,360,600,37);
    stroke('#D4AF37');
    fill('#8b5d2e');
    rect(0,260,600,100);
    fill('#2F4F4F');
    stroke('#2F4F4F');
    line(300,0,300,15)
    line(220,15,382,15);
//  This for loop creates the changing greyscale shapes on top of the speakers and behind the overstage lights, as well as the overhead light structure.
    for (var i = 0; i < discoLighting.length; i++) {
        stroke('#2F4F4F');
        line(220 + 18 * i,15,220 + 18 * i,35);
        fill(20);
        rectMode(CENTER);
        rect(220 + 18 * i,35,15,30);
        fill(discoLighting[i]);
        ellipse(220 + 18 * i,50,15,5);
        noStroke();
        fill(discoLighting[i]);
//      This if statement handles the left speaker.
        if (i < 5) {
            rect(20 * i,197,18,7);
            rect(20 * i + 7.5,196,18,7);
            rect(20 * i + 15,194,18,7);
        }
//      This if statement handles the right speaker.
        if (i >= 5) {
            rect(20 * (i - 5) + 510,197,18,7);
            rect(20 * (i - 5) + 502.5,196,18,7);
            rect(20 * (i - 5) + 495,194,18,7);
        }
//      Creates the actual speaker structures on the side speakers.
        rectMode(CORNER);
        stroke('#2F4F4F');
        strokeWeight(2);
        fill(90);
        rect(0,200,81,100);
        quad(82,200,103,193,103,300,82,300);
        rect(509,200,91,100);
        quad(509,200,487,193,487,300,509,300);
        fill('#778899');
        ellipse(32,230,45,45);
        ellipse(559,230,45,45);
        ellipse(32,280,25,25)
        ellipse(559,280,25,25);
        noStroke();
        fill(230);
        ellipse(32,230,5,5);
        ellipse(32,280,3,3);
        ellipse(559,230,5,5);
        ellipse(559,280,5,5);
        strokeWeight(1);
    }
//  Creates the steps the dancers are on.
    rectMode(CORNER);
    stroke('#D4AF37');
    fill('#52361b');
    rect(35,270,530,50);
    rect(95,200,400,40);
    rect(145,160,300,20);
    fill('#8b5d2e');
    quad(35,270,95,240,495,240,565,270);
    quad(95,200,145,180,445,180,495,200);
    quad(145,160,445,160,405,150,185,150); 
}
// This function shuffles an array's contents randomly, and is used to shuffle the greyscale shapes behind the lights for the discoLighting array.
function shuffler(array) {
    var currentPos = array.length, tempValue, randomPos;
    while (currentPos !== 0) {
        randomPos = Math.floor(Math.random() * currentPos);
        currentPos --;
        tempValue = array[currentPos];
        array[currentPos] = array[randomPos];
        array[randomPos] = tempValue;
    }
    return array;
}
// This function displays the white keys on the canvas, and alot of other things documented within the function.
function whiteKeys() {
    rectMode(CORNER);
//  Iterates through the keys found in the whiteKey dictionary, and assigns the corresponding position to the x variable.
    for (var key in whiteKeyToX){
        var x = whiteKeyToX[key];
//      Does alot of stuff when the key is pressed.
        if (keyIsDown(key.charCodeAt(0))){
//          Increases the number of presses of the particular key within the numberOfPresses Array.
            var particularClick = keyItoClicks[key];
            numberOfPresses[particularClick] += 1;
//          Displays the key on the canvas
            fill(255);
            stroke(0);
            rect(x,410,wKeyW,wKeyH);
//          Dictionary to associate the sound variables defined at the beginning with the corresponding key.
            var whiteKeyToSound = {'Q': cO1, 'W': dO1, 'E': eO1, 'R': fO1, 'T': gO1, 'Y': aO1, 'U': bO1, 'Z': cO2, 'X': dO2, 'C': eO2, 'V': fO2, 'B': gO2, 'N': aO2, 'M': bO2};
            var keySound = whiteKeyToSound[key];
//          Plays the actual sound without looping.
            if (!wasPressed[key]) {
                keySound.play();
                wasPressed[key] = true;
            }
//          To reduce clutter on the screen, placed the visuals within the noteComesOut function.
            if (noteComesOut()) {
//              Creates a note object in the notesArray.
                notesArray.push(makeNote(x,keyToColorHex[key]));
//              Makes a light based on the parameters defined within this function.
                makeLight(x,keyToColorHex[key],key,particularClick);
//              Shuffles the discoLighting array everytime a note comes out.
                discoLighting = shuffler(discoLighting);
//              Makes the larger circle at the top of each speaker pulse each time a note comes out.
                strokeWeight(3);
                fill('#778899');
                stroke('#2F4F4F');
                var pulse = random(-5,5);
                ellipse(32,230,50 + pulse,50 + pulse);
                ellipse(559,230,50 + pulse,50 + pulse);
                strokeWeight(1);
                fill(230);
                noStroke();
                ellipse(32,230,5,5);
                ellipse(559,230,5,5);
            }
//      This draws the key in the normal, unpressed position and lets you play the sound again the next time you press the key down.
        } else {
            wasPressed[key] = false;
            fill(255);
            stroke(0);
            rect(x,410,wKeyW,wKeyH - whiteBottomHeight);
            fill(150);
            rect(x,410 + wKeyH - whiteBottomHeight, wKeyW, whiteBottomHeight);
        }
    }
}
// This function displays the black keys on the canvas, and alot of other things documented within the function.
function blackKeys() {
    rectMode(CORNER);
//  Iterates through the keys found in the blackKey dictionary, and assigns the corresponding position to the x variable.
    for (var key in blackKeyToX){
        var x = blackKeyToX[key];
//      Does alot of stuff when the key is pressed.
        if (keyIsDown(key.charCodeAt(0))){
//          Increases the number of presses of the particular key within the numberOfPresses Array.
            var particularClick = keyItoClicks[key];
            numberOfPresses[particularClick] += 1;
//          Displays the key on the canvas
            fill(60);
            stroke(60);
            rect(x,400,bKeyW,bKeyH);
//          Dictionary to associate the sound variables defined at the beginning with the corresponding key.
            var blackKeyToSound = {'2': cSO1, '3': dSO1, '5': fSO1, '6': gSO1, '7': aSO1, 'S': cSO2, 'D': dSO2, 'G': fSO2, 'H': gSO2, 'J': aSO2};
            var keySound = blackKeyToSound[key];
//          Plays the actual sound without looping.
            if (!wasPressed[key]) {
                keySound.play();
                wasPressed[key] = true;
            }
//          To reduce clutter on the screen, placed the visuals within the noteComesOut function.
            if (noteComesOut()) {
//              Creates a note object in the notesArray.
                notesArray.push(makeNote(x,keyToColorHex[key]));
//              Makes a light based on the parameters defined within this function.
                makeLight(x,keyToColorHex[key],key,particularClick);
//              Shuffles the discoLighting array everytime a note comes out.
                discoLighting = shuffler(discoLighting);
//              Makes the larger circle at the top of each speaker pulse each time a note comes out.
                strokeWeight(3);
                stroke('#2F4F4F');
                fill('#778899');
                var pulse = random(-5,5);
                ellipse(32,230,50 + pulse,50 + pulse);
                ellipse(559,230,50 + pulse,50 + pulse);
                fill(230);
                strokeWeight(1);
                noStroke();
                ellipse(32,230,5,5);
                ellipse(559,230,5,5);
            }
//      This draws the key in the normal, unpressed position and lets you play the sound again the next time you press the key down.
        } else {
            wasPressed[key] = false;
            fill(60);
            stroke(60);
            rect(x,400,bKeyW,bKeyH - blackBottomHeight);
            fill(30);
            stroke(30);
            rect(x,400 + bKeyH - blackBottomHeight, bKeyW, blackBottomHeight);
        }
    }
}
// This function shows what keyboard key corresponds to what piano key.
function showControls() {
    textAlign(CENTER,CENTER);
    textSize(20);
//  White keys are displayed with black text.
    fill(0);
    text("Q",40,570);
    text("W",80,570);
    text("E",120,570);
    text("R",160,570);
    text("T",200,570);
    text("Y",240,570);
    text("U",280,570);
    text("Z",320,570);
    text("X",360,570);
    text("C",400,570);
    text("V",440,570);
    text("B",480,570);
    text("N",520,570);
    text("M",560,570);
//  Black keys are displayed with white text.
    fill(255);
    text("2",60,520);
    text("3",100,520);
    text("5",180,520);
    text("6",220,520);
    text("7",260,520);
    text("S",340,520);
    text("D",380,520);
    text("G",460,520);
    text("H",500,520);
    text("J",540,520);
}
// This function is just for clarity and structure.
function piano() {
    whiteKeys();
    blackKeys();
}

function setup() {
    createCanvas(600,602);
//  Loading the numberOfPresses Array to track amount of presses of each key.
    for (var i = 0; i < 24; i++) {
        numberOfPresses[i] = 0;
    }
//  Sets up arrays that need to be present before draw is called.
    loadAnimationArray(); 
    setDancesForPeople();
}

function draw() {
    background(30);
//  Calling corresponding functions documented above.
    pianoStructureAndLights();
    piano();
    checkAndUpdateDancing();
    displayNotes();
    checkAndUpdateNotes();
    removeOffScreenNotes();
//  Visually representing and calling the function to display controls if mouse is hovered over it.
    stroke(220);
    fill(100);
    rect(490,0,120,20);
    textSize(12);
    textAlign(LEFT,BOTTOM);
    stroke(180);
    fill(180);
    text("Hover For Controls",495,17);
    if (mouseX >= 490 & mouseY <= 20) {
        showControls();
    }
}

To run this project, please download the zip file located below, and then create a local server so that the sounds can be loaded properly.

104 final project zip file

This project uses keyboard input to press the corresponding keys, which are shown when hovering over the area displayed in the top right corner of the canvas. When the key is pressed, the corresponding note is played, a note with a color corresponding to the key and varying properties including fadeout rate, vertical speed, and note tail orientation is created. The amount of notes created depends on how long the key is pressed down for. Lights on top of the speakers on each side of the stage and at the top display the
corresponding color, ending at the corresponding dancer location. The dancer at that location also cycles through its animation, and remembers what frame it was on and pauses when the key is not pressed. There are four different types of dances, based on key position. Grey flickering lights, somewhat resembling a glowing disco ball (which is why its called discoLighting in the code) also randomly flicker both above the speakers and at the top behind the lights when a key is pressed.

This project uses free sprite sheets available for download at: https://www.deviantart.com/gladkirby7/art/Vexy-Dances-FruityDance-Sprite-Sheet-675963897 for the arm dance and windmill dance. https://www.pinterest.com/pin/525021269044190160/?lp=true for the spin dance.

https://blueprintinteractive.com/sites/default/files/images/carlton_sprites2.png for the man dance.

The sounds for each piano key were downloaded from https://freesound.org/people/jobro/packs/2489/.

We learned a lot about code structure when making this, and also about how useful dictionaries are for converting information in an organized way. Overall we are pretty pleased with the project and how it can handle many keys being pressed without slowing down at all.

Please see the note in the comments at the top of the code for how we would like to be graded on this project.

Leave a Reply