Music Composer

About:
The Music Composer is an ongoing project created for the Town and Country Learning Center in southeast San Diego as a part of UCSD’s Global TIES program. Team page is here: http://globalties.ucsd.edu/tnc.html

Global TIES:
“is an innovative humanitarian engineering program of the Jacobs School of Engineering at the University of California, San Diego.  Global TIES puts multi-disciplinary teams of undergraduates to work building the dreams of not-for-profit organizations and their clients in San Diego and in developing countries around the world.  Renowned UC San Diego faculty and researchers advise the teams, and students receive course credit for their work.  Global TIES teams give students an invaluable opportunity to apply their skills in a real world setting, while learning firsthand the role that engineering and technology can play in solving the problems that face their local community and the world.  Not-for-profit organizations receive critically needed but often cost-prohibitive technical expertise to help them improve the lives of their clients.”

Town and Country Learning Center:
“The Town and Country Village Learning Center is a community organization located in Southeast San Diego. The Learning Center provides computing facilities, space for homework and study, and various other educational, afterschool activities such as cooking, dance, or gardening to children and young adults who live there.”

The music composer’s goal is to introduce the students at the learning center to basic music concepts. It allows them to compose and playback their own musical compositions.

My contributions:
-Music Composer subteam  leader for Winter quarter 2012
-Expansion of staffs
-Implementation for changing note lengths
-Incorporating piano midi sounds
-GUI design and layout
-Groundwork laid for tempo and instrument changes

Future Plans:
-Tempo changing
-Instrument changing
-Sharps and flats
-Use with DDR pad, piano keyboard pad, and Kinect

Developer Details:
-Written in Processing language

Screen Shot:
music game

Source Code: 


import ddf.minim.*;
import ddf.minim.signals.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import arb.soundcipher.*;
// *Had to use Processing ver. 1.2.1 (not 1.5) to export applet and application properly

//-----------------------------------------------------------
// MUSIC COMPOSITION EDITOR/GAME IN PROCESSING .pde
// Laura Park, Cindy Lee, Luisa Leong
// May 2011
// TIES Town & Country
// Winter 2012:
// Rickcel Bantog, Kayvon Ghaffari, Henry Lin
//
/** This game is meant to introduce users to Western musical notation and composition.
* No musical background necessary!
* Simply begin to compose your own music by pressing the arrow keys and Enter/Return key to play.
**/
/*
 SET UP:
 - Don't forget to put the proper library folder for sound cipher into the libraries folder for processing
 - check http://explodingart.com/soundcipher/ for details for your OS of choice

 Details:
 - First note is set to C5(middle C is C4)

- Current key commands: UP, DOWN, LEFT, RIGHT, ENTER
 (Change pitch, change edit cursor, play all)

- Current sound choices: sound cipher
 http://explodingart.com/soundcipher/

 - Change note length with space bar

- Future expansion:
 - Maybe have it so that when you push right it doesn't always start at C, rather the same note as previous
 - saving feature
 - flats/sharps: grab midi numbers from http://www.phys.unsw.edu.au/jw/notes.html and add in some drawing for the flat and sharp sign
 - different instruments: http://explodingart.com/soundcipher/tutes/falling_instruments/falling_tute.html
 - chords: http://explodingart.com/soundcipher/tutes/play_chord/play_chord_tute.html
 - implement use with different inputs: DDR pad, piano pad, Kinect, mind powers
 - time/measures
 - bass clef/polyphony

---------------------------------------------------------------------

Notes and Current Bugs:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 - make good use of processing libraries like sound cipher, lots of tutorials and example code are provided to make life easier

 - Polyphony could be implemented by having another key (like SPACE) designate a written note, then using LEFT go back and add a new Note object (and don't edit existing note at that index)
 --would probably need an array of notes for each index (so each note in the array is a note to be played simultaneously with the other notes in that array)

 - Drawing stuff on here is done by placement of x and y pixels on the image so it kind of sucks pretty bad in that way if you want to change the look

 B U G S :
 - High A and low C are drawn without the line through on any line past the first one
 - just need to play with y coordinate value in logic

 - High A will overlap with low C on line above.
 - Just need to adjust background image so that there is more space between lines, this would require changing a lot of logic since
 everything is based on x and y values.
 - Would be easy to set some static constants to refer back to them to make for easier changes for future

*/
Minim minim;
AudioOutput out;

SoundCipher sc = new SoundCipher(this); //used to play notes
PImage bg; // staff bg - add clefs

int maxN = 100; // number of notes allowed (in future, can expand to more lines)
int index = 0; // note index
int linenum = 0;
int counter = 1;
Note[]notes = new Note[maxN]; // array of notes
int[]test = new int[maxN];
int cPos = 114; // pitch C y-coord on line 1
//int n = 120; // note's coordinates
int x,y;// cursor coordinates
int setline = 0;
PFont f,f2;
String message = "Press ENTER to play all the notes you've written!";
String usage1 = "Use right & left arrow keys to set position of current note.";
String usage2 = "Use up & down keys to choose the note's pitch(frequency). Push space bar to change note length.";
String version = "version 1.0";

//13 total notes

private static final int defaultPitch = 60; //default note gets these values (quarter note)
private static final int defaultLoudness = 100;
private static final int defaultLength = 2;
//All notes that are lower in pitch than the default note
private static final int pitchB3 = 59;
private static final int pitchA3 = 57;
private static final int pitchG3 = 55;
private static final int pitchF3 = 53;
private static final int pitchE3 = 52;
private static final int pitchD3 = 50;
private static final int pitchC3 = 48;

//All notes that are higher in pitch than the default note
private static final int pitchD4 = 62;
private static final int pitchE4 = 64;
private static final int pitchF4 = 65;
private static final int pitchG4 = 67;
private static final int pitchA4 = 69;

private int[] scaleH = {pitchC3, pitchD3, pitchE3, pitchF3, pitchG3, pitchA3, pitchB3, defaultPitch, pitchD4, pitchE4, pitchF4, pitchG4, pitchA4};
private int scaleIndex = 7;

void setup() {
 // music sheet size
 size(800, 600);//650, 600); // in future, can expand to more lines
 x=100;
 y=linenum*124 +114; // initial coordinates for first note
 bg = loadImage("staffbg.png"); // load background image; file is in 'data' folder
 f = createFont("Arial",18,true); // for messages
 f2 = createFont("Arial",14,true);
 minim = new Minim(this); // audio library
 out = minim.getLineOut(Minim.STEREO);
 for (int j = 0; j < notes.length; j++) {
 if (j % 16 == 0 && j !=0){
 setline++;
 }
 notes[j]= new Note((100 + ((j%16)*45)), setline*124 +114);
 } // create all Note objects (displayed in draw())
}

void draw() {
 background(300);
 image(bg,-10,40); // place bg image

 // text messages
 fill(153); // version text in gray
 textFont(f2);
 text(version, 700, 17);
 fill(155); //text messages in black
 textFont(f2);
 text(usage1, 8 ,17);
 text(usage2, 8 ,35);
 fill(0); //text messages in black
 textFont(f);
 text(message, 8, 54);

 // display notes

 notes[index].display(); // current note
 for(int d = 0; d <= index; d++) {
 // System.out.println("note location: (" + notes[d].xcoord + "," + notes[d].ycoord + ")");
 System.out.println("index d: " + d);
 System.out.println("index: " + index);
 System.out.println("linenum: " + linenum + " y: " + y );
 notes[d].display(); // past/written notes
 }
}

// to update notes when user hits keys
void keyReleased() {
 if (keyCode == DOWN) {
 System.out.println(scaleH[scaleIndex]);
 // check for staff height
 if (scaleIndex > 0) {

 scaleIndex--;

 y = y + 10; // moves note up a half step
 keyCode = 0; // unnecessary?
 notes[index].moveY(y);
 System.out.println("in down if");
 notes[index].setPitch(scaleH[scaleIndex]);
 notes[index].setScaleIndex(scaleIndex);
 System.out.println("in down if 2");

 // set new note letter
 if (notes[index].getLetter().equals("C")) {
 notes[index].setLetter("B");
 } else if (notes[index].getLetter().equals("B")) {
 notes[index].setLetter("A");
 } else if (notes[index].getLetter().equals("A")) {
 notes[index].setLetter("G");
 } else if (notes[index].getLetter().equals("G")) {
 notes[index].setLetter("F");
 } else if (notes[index].getLetter().equals("F")) {
 notes[index].setLetter("E");
 } else if (notes[index].getLetter().equals("E")) {
 notes[index].setLetter("D");
 } else if (notes[index].getLetter().equals("D")) {
 notes[index].setLetter("C");
 }

 notes[index].playNote();
 }
 }

 if(keyCode == UP) {
 // range check for staff height !!!!
 // if (y - 10 > 55 + linenum*124 ) {
 if (scaleIndex < scaleH.length - 1) {
 scaleIndex++;

 y = y - 10; // moves note down a half step
 keyCode = 0;// unnecessary?
 notes[index].moveY(y);
 notes[index].setPitch(scaleH[scaleIndex]);
 notes[index].setScaleIndex(scaleIndex);

 // set new note letter
 if (notes[index].getLetter().equals("C")) {
 notes[index].setLetter("D");
 } else if (notes[index].getLetter().equals("D")) {
 notes[index].setLetter("E");
 } else if (notes[index].getLetter().equals("E")) {
 notes[index].setLetter("F");
 } else if (notes[index].getLetter().equals("F")) {
 notes[index].setLetter("G");
 } else if (notes[index].getLetter().equals("G")) {
 notes[index].setLetter("A");
 } else if (notes[index].getLetter().equals("A")) {
 notes[index].setLetter("B");
 } else if (notes[index].getLetter().equals("B")) {
 notes[index].setLetter("C");
 println (index);
 }

 notes[index].playNote();

 }
 }
 if(keyCode == RIGHT) { // moves edit cursor, will leave previous note written
 System.out.println("index: " + index);

 // can expand to more lines
 //reset scale index
 scaleIndex = 7;
 if ( index < maxN)
 {
 index++; // move cursor to next note
 }

 if((index)%16 > 0){//if ( x + 45 < width ) {
 // prep coordinates of next note
 counter = 1;
 x = x + 45;
 y = linenum*124 +114 ;
 notes[index].moveY(y);
 keyCode = 0;
 } else { // new line
 x = 100;
 linenum++;
 y = linenum*124+114;
 notes[index].moveY(y);
 }
 }
 if (keyCode == LEFT || keyCode == BACKSPACE) { // move edit cursor, now can edit pitch of previous note
 System.out.println("line num: " + linenum);
 if (index > 0){
 //if reaching end of line
 if((index)%16 == 0)
 {
 if (linenum > 0) {
 linenum--;
 }

x = 100;
 y = notes[index-1].getY(); // update cursor to previous note's pitch
 scaleIndex = notes[index-1].getScaleIndex();
 notes[index].reset(); // "erase"
 index--;
 }
 else{

 counter = notes[index-1].getNoteLength();
 x = x - 45;
 y = notes[index-1].getY(); // update cursor to previous note's pitch
 scaleIndex = notes[index-1].getScaleIndex();
 notes[index].reset(); // "erase"
 index--;
 }
 }
 }
 if(keyCode == ENTER || keyCode == RETURN){ // Play all written notes
 //play all notes using play phrase function of sound cipher
 //takes in input as arrays
 float [] pitches = new float[index + 1];
 float [] dynamics = new float[index + 1];
 float[] durations = new float[index + 1];
 //old way had <= index
 for(int i = 0; i <= index; i++) {
 //old way notes were played, notes not played at correct length
 //notes[i].playNote();
 //delay(300);

 pitches[i] = notes[i].getPitch();
 //no current method to change loudness, for future adjust here
 dynamics[i] = defaultLoudness;
 //when playing a note need to do 2^ of note length
 //added in /2 because it seemed slow
 durations[i] = (float) pow(2, notes[i].getNoteLength() / 2);
 }

 //to change instrument
 //sc.instrument = sc.BASSOON;
 //to change tempo
 //sc.tempo(900);
 sc.playPhrase(pitches, dynamics, durations);
 }

 /* space bar push
 */
 if(keyCode == ' ') {
 /*
 0 = quarter
 1 = whole
 2 = half
 3 = eigth*/
 int tempCount = notes[index].getNoteLength();
 tempCount++;
 keyCode = 0;
 if (tempCount > 3){
 tempCount = 0;
 }
 //change current note length here
 notes[index].setNoteLength(tempCount);
 }
}

//--------------------------------------------------------------
// Note class
//--------------------------------------------------------------
// Each note has x and y coordinates
// ellipse body and line stem
// sine wave
//
//--------------------------------------------------------------
class Note {
 int xcoord, ycoord;
 int pitch;
 int noteLength;
 String letter;
 int lengthCounter; //
 int noteScaleIndex;
 //SineWave sine;

 Note(int xx, int yy) {
 xcoord = xx;
 ycoord = yy;
 noteLength = 1;
 pitch = defaultPitch; // middle C is 262
 letter = "C"; // note/pitch letter
 noteScaleIndex = 7;
 //sine = new SineWave(pitch, 0.5, out.sampleRate());
 //sine.portamento(200);
 }

// *Notes should not move horizontally, only the edit cursor
 // moves left and right to select which note to edit
 //void moveX(int xx) {
 // xcoord = xx;
 //}
 void moveY(int yy) {
 ycoord = yy;
 }
 int getY() {
 return ycoord;
 }

 int getNoteLength(){
 return noteLength;
 }

 //update note length, called when space bar is pushed
 void setNoteLength(int num){
 noteLength = num;
 }
 void display() {
 int counter;
 counter = noteLength;
 switch(counter) {
 case 0: // eigth note
 fill(1); // put note body and stem in black
 ellipse(xcoord, ycoord, 30, 20); // note body

 if (ycoord > 114 && ycoord < 185 | (ycoord > 240 && ycoord < 311) | (ycoord > 366 && ycoord < 436) | ( ycoord > 492 && ycoord < 563)) { // switch placement of note stem
 line (xcoord+15, ycoord, xcoord+15, ycoord-40); //stem
 noFill();
 bezier(xcoord + 15, ycoord-40, xcoord+ 30, ycoord - 10, xcoord + 30, ycoord -10, xcoord + 25, ycoord);
 } else {
 line (xcoord-15, ycoord, xcoord-15, ycoord+40); //sptem
 noFill();
 bezier(xcoord-15, ycoord+40, xcoord+5, ycoord + 30, xcoord + 10, ycoord+10, xcoord, ycoord+20);
 }

 break;
 case 1: // quarter note

 fill(1); // put note body and stem in black
 ellipse(xcoord, ycoord, 30, 20); // note body

 if (ycoord > 114 && ycoord < 185 | (ycoord > 240 && ycoord < 311) | (ycoord > 366 && ycoord < 436) | ( ycoord > 492 && ycoord < 563)) { // switch placement of note stem
 line (xcoord+15, ycoord, xcoord+15, ycoord-40); //stem
 } else {
 line (xcoord-15, ycoord, xcoord-15, ycoord+40); //stem
 }
 break;

 case 2: // half note
 fill(255,255,255);
 ellipse(xcoord, ycoord, 30, 20); // note body
 if (ycoord > 114 && ycoord < 185 | (ycoord > 240 && ycoord < 311) | (ycoord > 366 && ycoord < 436) | ( ycoord > 492 && ycoord < 563)) { // switch placement of note stem
 line (xcoord+15, ycoord, xcoord+15, ycoord-40); //stem
 } else {
 line (xcoord-15, ycoord, xcoord-15, ycoord+40); //stem
 }
 break;

 case 3: // whole note

 fill(255,255,255);
 ellipse(xcoord, ycoord, 30, 20); // note body

 break;
}

// conditional (on high B and middle C) slash through note
 if (ycoord == 64 || ycoord == 184 || ycoord == 190 || ycoord == 310 || ycoord == 316 || ycoord == 436 || ycoord == 442 || ycoord == 562) {
 line (xcoord-25, ycoord, xcoord+25, ycoord);
 }
 strokeWeight(4);

 fill(255,0,0); // put letter in white
 text(letter,xcoord-5,ycoord+5); // place note/pitch letter on top of note body
 }
 int getPitch() {
 return pitch;
 }
 void setPitch(int p) {
 pitch = p;
 }
 void playNote() {
 //test
 //(pitch, loudness, length)
 sc.playNote(pitch, defaultLoudness, (double) pow(2, noteLength));
 }
 // Reset note to C5
 void reset() {
 noteScaleIndex = 7;
 noteLength = 1;
 this.moveY(cPos + linenum*124); // "erase" by resetting pitch to C
 this.setPitch(defaultPitch);
 this.setLetter("C");
 }
 // Note Letter
 String getLetter() {
 return letter;
 }
 void setLetter(String l) {
 letter = l;
 }
 void setScaleIndex(int tempNum){
 noteScaleIndex = tempNum;
 }
 int getScaleIndex(){
 return noteScaleIndex;
 }

 //
 //void stopNote() {
 // out.close();
 // minim.stop();
 //}
}

Advertisements