Mr

 Mr. Rogers' IB Computer Science -- Android SmartPhone Programming Project
Help boost K-12 science, math, and computer education. Donate your obsolete Android phone to us (see below)! We promise to put it to good use in a classroom.

Mr Rogers Android Project Menu

Home

Projects

2010-2011


   3D Compass

   3D Accelerometer

   Friction Tester

   Drum

   Spherical Game

   Level

 


Greenville, SC
Rhythm Notater
Daniel Vu



Description
This application is used to transcribe musical rhythms. The accelerometer is used to input rhythmic values. These values are then quantized and converted into note values. This is then drawn onto a canvas.

Screenshots
Screen 1
Screen 2

 

Features
  • Working metronome that provides vibration and audio cues.
  • Provides time between each stroke using the accelerometer.
Version
0.3 as of January 7, 2011

Future Plans

  • Drawing rhythms on the canvas
  • Saving values into an SQLLite database.
  • Bluetooth between devices, allowing for realistic drumming
  • Transcribing dynamics and articulations
  • Using the orientation sensor in order to create different pitches
Instructions
Once the application is open, you will be presented with the metronome. Clicking on the up and down buttons will changing the BPM value. Clicking start will start the metronome.

Classes and Important Fields
You can find the code by clicking the links.




Code

Primary

package nr.co.dv297.rhythmnotater;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class DataView extends Activity implements OnClickListener{

        /**
         *  Remember we eventually need to close the database in the main class!
         */

        private ListView lv;
        private ArrayList<String> queryString = new ArrayList<String>();
        private Cursor data_cursor;
        private ArrayAdapter<String> aa;
        private Button return_button;
        private static String selectedString;

        public void onCreate(Bundle savedInstanceState){
                super.onCreate(savedInstanceState);
                setContentView(R.layout.datasheet);

                return_button = (Button) findViewById(R.id.returnbutton);
                return_button.setOnClickListener(this);

                Main.addDBItem(12312);
                //Main.getDatabase().notify();

                data_cursor = Main.getDatabase().query("Rhythms", null, null, null, null, null, null);
                startManagingCursor(data_cursor);
                fillAllLists();

                lv = (ListView) findViewById(R.id.DataListView);
                aa = new ArrayAdapter<String>(this,
                                android.R.layout.simple_list_item_1, queryString);
                lv.setAdapter(aa);
                data_cursor.requery();


                lv.setOnItemClickListener(new OnItemClickListener() {

                        public void onItemClick(AdapterView<?> arg0, View view, int point,
                                        long id) {
                                selectedString = ((String) ((TextView) view).getText());
                                Intent myIntent = new Intent(view.getContext(),
                                                NoteDetail.class);
                                startActivityForResult(myIntent, 0);
                        }
                });

        }

        public void fillAllLists() {
                queryString.clear();
                if (data_cursor != null) {
                        data_cursor.moveToFirst();
                        while (!data_cursor.isAfterLast()) {
                                String s = data_cursor.getString(1);
                                queryString.add(s);
                                data_cursor.moveToNext();
                        }
                }
        }


        @Override
        public void onClick(View v){
                if(v == return_button){
                        Intent intent = new Intent();
                        setResult(RESULT_OK, intent);
                        finish();
                }

        }

        public static String getSelectedString(){
                return selectedString;
        }

}

Main

package nr.co.dv297.rhythmnotater;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class Main extends Activity implements OnClickListener {

  //private static RhythmPanel paintview; // Supplements bottom half of GUI.
  private static LinearLayout parentcontainer, paintcontainer; 
  // ^^ paintcontainer is the bottom half of the GUI, visual aid to metronome
  private static Button add_button, subtract_button, start_button, erase_button,
      notate_button;
  private static TextView bpmvalue, textview01, textview02;
  private static boolean running;    // Shows if the Metronome met is running.
  private Shaker shaker = null;     // Allows the input of shake events.
  private Metronome met; // Controls timing mechanisms.



  private static int bpmcount = 120// Parallel value to bpmvalue
  private static SQLiteDatabase db; // Allows the storage of rhythms.


  

  /** Called when the activity is first created. */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RhythmPanel rp_holder = new RhythmPanel(this);
    setContentView(R.layout.main);
    
    //rp_holder = new RhythmPanel(this);
    
    //paintview = (RhythmPanel) findViewById(R.id.SurfaceView01);
    start_button = (ButtonfindViewById(R.id.startbutton);
    add_button = (ButtonfindViewById(R.id.add_button);
    subtract_button = (ButtonfindViewById(R.id.subtract_button);
    erase_button = (ButtonfindViewById(R.id.erase_button);
    notate_button = (ButtonfindViewById(R.id.notate_button);
    bpmvalue = (TextViewfindViewById(R.id.bpmvalue);
    parentcontainer = (LinearLayoutfindViewById(R.id.parentcontainer);
    paintcontainer = (LinearLayoutfindViewById(R.id.paintcontainer);
    
    textview01 = (TextViewfindViewById(R.id.TextView01);
    textview02 = (TextViewfindViewById(R.id.TextView02);

    add_button.setOnClickListener(this);
    subtract_button.setOnClickListener(this);
    erase_button.setOnClickListener(this);
    notate_button.setOnClickListener(this);
    start_button.setOnClickListener(this);
    
    met = new Metronome(this);

  }

  /**
   * Allows for buttons to work.
   */
  @Override
  public void onClick(View v) {

    if (v == start_button) {

      if(!running){
        
        start_button.setText("Stop");
          //paintview.setX(50);
          db = (new MyDBOpenHelper(getBaseContext())).getWritableDatabase();
          db.execSQL("DELETE FROM Rhythms");
          running = true
          met.startTimer();
          shaker = new Shaker(this);

      }
      else{
        met.resetValues();
        start_button.setText("Start");
        running = false
        met.stopMetronome();
        shaker.stopListeners();
        //db.close();
      }
      

    }
    if (v == add_button) {
      bpmcount = bpmcount + 1;
      bpmvalue.setText(bpmcount + "");
    }
    if (v == subtract_button) {
      bpmcount = bpmcount - 1;
      bpmvalue.setText(bpmcount + "");
    }
    if (v == erase_button) {
    }
    if (v == notate_button) {
//      MediaPlayer bob = MediaPlayer.create(this, R.raw.beep2);
//      bob.start();
      Intent myIntent = new Intent(v.getContext(), DataView.class);
      startActivityForResult(myIntent, 0);
    }
  }
  
  /**
   * Convenience method to add new notes to the rhythm queue. 
   @param difference
   */
  
  public static void addDBItem(float difference){
    ContentValues cv = new ContentValues();
    cv.put("Note_Length", difference);
    db.insert("Rhythms", null, cv);
  }

  public static LinearLayout getParentContainer() {return parentcontainer;}
  public static TextView getTextview01() {return textview01;}
  public static TextView getTextview02() {return textview02;}
  //public static RhythmPanel getPaintView(){return paintview;}
  public static boolean getRunning(){return running;}
  public static int getBpmcount() {return bpmcount;}
  public static TextView getBpmValue(){return bpmvalue;}
  public static Button getStartButton(){return start_button;}
  public static void setRunning(boolean b){running = b;}
  public static void setBpmCount(int bpmcount) {bpmcount = bpmcount;}

  public static SQLiteDatabase getDatabase() {
    return db;
  }
}

Metronome

package nr.co.dv297.rhythmnotater;

import android.app.Activity;
import android.content.Context;
import android.os.CountDownTimer;
import android.os.Vibrator;

/**
 @author Daniel Vu
 
 *         Metronome provides for the time based events in the program. Uses a
 *         CountDownTimer that will beat for eight 4/4 measures. Also contains
 *         the code for the Vibrator.
 
 */

public class Metronome {


  private int number_of_beats = 24;
  private double total_time, beats_per_minute, time_per_beat,
      time_remaining = 30000, current_beat = 1;
  // ^^^ Used for converting the BPM into second values.
  private CountDownTimer cdt; // Controls the timing mechanism for the
                // Vibrator, sound, and visual metronome.
  private double tpb; // Stores the time for each beat, in milliseconds.
  private Vibrator v; // Vibrates the phone on the beat
  private Activity activity; // Allows the program to get the Vibrator service
  private SoundManager sm; // Allows for the playing of sound
  private static boolean tapOffMode = true// When the metronome is started, a count
                    // down pattern of 8 beats will allow
                    // the user to prepare.
  private int currentTap = 1;   // Connected to boolean tapOffMode, states which sound will be played in the preparation time.

  /**
   * Initiliizes the Vibrator and SoundManager
   
   @param a
   *            Inputs the Activity from the previous Activity, allows for the
   *            use of Context.VIBRATOR_SERVICE
   
   */
  public Metronome(Activity a) {
    activity = a;
    v = (Vibratoractivity.getSystemService(Context.VIBRATOR_SERVICE);
    sm = new SoundManager(a);

  }

  /**
   * Convenience method to start CountDownTimer cdt
   */
  public void startTimer() {
    calculateTPB();

    cdt = new CountDownTimer((long) (total_time)(inttime_per_beat) {
      int x = 50;

      public void onTick(long millisUntilFinished) {
        v.cancel();
         v.vibrate(250);
        calculateTPB();

        if (tapOffMode) {
          if(currentTap == || currentTap == || currentTap == || currentTap == || currentTap == || currentTap == 8){
            sm.playSound(2);
            currentTap++;
            Main.getTextview02().setText("Tap Off 1");
            if(currentTap == 9){
              currentTap = 1;
              tapOffMode = false;
            }
          }
          else{
            sm.playSound(1);
            currentTap++;
            Main.getTextview02().setText("Tap Off 2");
          }
            

        else {
          //Main.getTextview02().setText(
          //    "seconds remaining: " + millisUntilFinished / 1000
          //        + " Current Beat: " + current_beat);
          //Main.getTextview01().setText("" + tpb);
          //Main.getPaintView().setX(100 - x);
          x = -x;
          sm.playSound(1);
          current_beat += 1;
          if (current_beat > 4)
            current_beat = 1;
        }
      }

      public void onFinish() {
        Main.getTextview02().setText("done!");
        Main.getStartButton().setText("Start");
        Main.setRunning(!Main.getRunning());
        tapOffMode = true;
      }
    }.start();
  }

  /**
   * Takes TextView bpmvalue in Main and calculates how many milliseconds to
   * allocate to each beat.
   */
  public void calculateTPB() {
    beats_per_minute = Integer.parseInt((StringMain.getBpmValue()
        .getText());
    tpb = (60 / beats_per_minute1000;
    time_per_beat = (inttpb;
    total_time = (number_of_beats+2* time_per_beat;
    
  }

  /**
   * Allows for the user to stop the metronome before 4 measures.
   */
  public void stopMetronome() {
    v.cancel();
    cdt.cancel();
  }
  
  public void resetValues(){
    tapOffMode = true;
    currentTap = 1;
    
  }
  
  public static boolean getTapOffMode(){
    return tapOffMode;
  }
  
}

RhythmPanel

package nr.co.dv297.rhythmnotater;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

/**
 
 @author Daniel Vu
 
 * Uses the following tutorial for the canvas code
 * http://www.helloandroid.com/tutorials/how-use-canvas-your-android-apps-part-1
 *
 * RhythmPanel is a SurfaceView created on the bottom half of the GUI.
 * In the final project, this area will be used to transcribe the rhythms played on the device.
 * An instance of RhythmPanel can be found in the Main class.
 */

public class RhythmPanel extends SurfaceView implements SurfaceHolder.Callback{
  private int x= 10, y=10;
  private int xchange = 0, ychange = 0, canvas_height, canvas_width;
  PaintThread newthread;
  
  public RhythmPanel(Context context) {
        super(context);
        getHolder().addCallback(this);
        newthread = new PaintThread(getHolder()this);
}
  
  public RhythmPanel(Context context, AttributeSet attrs){
    super(context, attrs);
        getHolder().addCallback(this);
        newthread = new PaintThread(getHolder()this);
  }

       public int getX() {
    return x;
  }

  public void setX(int x) {
    this.x = x;
  }

  public int getY() {
    return y;
  }

  public void setY(int y) {
    this.y = y;
  }



  @Override
  public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    // TODO Auto-generated method stub
    
  }

  @Override
  public void surfaceCreated(SurfaceHolder holder) {
    //newthread.setRun(true);
    //newthread.start();
    
  }

  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
    // TODO Auto-generated method stub
    
  }
  
  @Override
  public void onDraw(Canvas canvas){
    Paint paint = new Paint();
    //paint.setColor(Color.RED);
    //Bitmap myIcon = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
    //canvas.drawColor(Color.WHITE);
    //canvas.drawBitmap(myIcon, getX(), getY(), null);
    //canvas.drawCircle(100+xchange,100+ychange,10, paint);
  }
}

MyDBOpenHelper

package nr.co.dv297.rhythmnotater;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDBOpenHelper extends SQLiteOpenHelper {
    
    private static final String DATABASE_NAME = "Rhythms";
  private static final int DATABASE_VERSION = 1;

  /**
   * Initializes the database helper.
   @param context
   */
    public MyDBOpenHelper(Context context) {
      super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    
    /**
     * Initializes the SQLiteDatabase db with table "Rhythms", columns "_id", int, and "Note_Length", float
     */
  @Override
  public void onCreate(SQLiteDatabase db){
    String sql = "CREATE TABLE Rhythms " 
    "(_id INTEGER PRIMARY KEY, " +
    "Note_Length FLOAT); ";
    db.execSQL(sql);
  }
  
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // TODO Auto-generated method stub
  }
}

PaintThread

package nr.co.dv297.rhythmnotater;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class PaintThread extends Thread{

  private SurfaceHolder surfaceholder; // Holds the canvas
  private RhythmPanel rhythmpanel; // A holder field that is passed from the constructor
  private boolean run; // States if the metronome is running.

  public PaintThread(SurfaceHolder sh, RhythmPanel rp){
    this.surfaceholder = sh;
    this.rhythmpanel = rp;
  }

  public void setRun(Boolean run){
    this.run = run;

  }

  /** 
   * Allows for the drawing of the canvas. 
   */
  public void run(){
    Canvas c;
    while(run){
      c = null;
      try{
        c = surfaceholder.lockCanvas(null);
        synchronized(surfaceholder){
          int lower = 0;
          int higher = 300;
          rhythmpanel.onDraw(c);
        }
      }
      
      finally{
        if(c !=null){
          surfaceholder.unlockCanvasAndPost(c);
          
        }
      }
    }
  }
}

Shaker

package nr.co.dv297.rhythmnotater;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

/** 
 * Uses the following tutorial for the SensorEventListener code
 * http://www.helloandroid.com/tutorials/using-android-phones-sensors
 
 * Shaker is a class that implements SensorEventListener. 
 * Its constructor initiates the Accelerometer sensor 
 
 */
public class Shaker implements SensorEventListener {

  private SensorManager sensorManager = null;
  private float[] values = 00}// Refer to the x, y, and z acceleration of the device
  private long ts1, ts2; // Used to calculate the time between shake events.
  private int number_of_notes = 0;

  public Shaker(Activity a) {
    sensorManager = (SensorManagera.getSystemService(Context.SENSOR_SERVICE);
    sensorManager.registerListener(this,
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
        SensorManager.SENSOR_DELAY_GAME);
    ts1 = System.currentTimeMillis();
    // sensorManager.registerListener(this,
    // sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
    // sensorManager.SENSOR_DELAY_GAME);
  }

  @Override
  public void onAccuracyChanged(Sensor sensor, int accuracy) {
    // TODO Auto-generated method stub

  }

  @Override
  public void onSensorChanged(SensorEvent event) {
    synchronized (this) {
      switch (event.sensor.getType()) {
      case Sensor.TYPE_ACCELEROMETER:
        values[0= event.values[0];
        values[1= event.values[1];
        values[2= event.values[2];
        ts2 = event.timestamp;
        long difference = ts2 - ts1;
        if (event.values[2>= 20 && Main.getRunning() && !Metronome.getTapOffMode()){
          Main.addDBItem(difference);
          number_of_notes++;
          Main.getTextview01().setText("Number of Notes " + number_of_notes);
        }
        

        break;
      // case Sensor.TYPE_ORIENTATION:
      // outputX2.setText("x:" + Float.toString(event.values[0]));
      // outputY2.setText("y:" + Float.toString(event.values[1]));
      // outputZ2.setText("z:" + Float.toString(event.values[2]));
      // break;
      }
    }
  }

  public void stopListeners() {
    sensorManager.unregisterListener(this);
  }

}

SoundManager

package nr.co.dv297.rhythmnotater;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Environment;

public class SoundManager {
  /**
   * Convenience class for starting sound
   * Not fully complete.
   */
  private boolean mExternalStorageAvailable = false;
  private boolean mExternalStorageWriteable = false;
  private String state = Environment.getExternalStorageState();
  private MediaPlayer mp1 = new MediaPlayer();
  private MediaPlayer mp2 = new MediaPlayer();
  private HashTranslater ht = new HashTranslater();
  private Context c;
  
  /**
   * Declares what the MediaPlayer is playing. 
   @param a
   */
  public SoundManager(Context a){
    c = a;
//    InputStream inputStream = getResources().openRawResource(R.raw.sound1);
    mp1 = MediaPlayer.create(c, R.raw.beep2);
    mp2 = MediaPlayer.create(c, R.raw.beep1);
  }
  
  public void preparePlayer(){
    try {
      mp1.reset();
      mp1.prepare();
      mp2.reset();
      mp2.prepare();
    
    catch (Exception e) {e.printStackTrace();}
  }
  
  public void playSound(int choice){
    if(choice == 1){
//      mp.reset();
//      AssetFileDescriptor fd = c.getResources().openRawResourceFd(R.raw.beep1);
//      try {
//        mp.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
//        mp.prepare();
//      } catch (Exception e) {e.printStackTrace();}
      mp1.start();
    }
    else if(choice == 2){;
//      mp.reset();
//      AssetFileDescriptor fd = c.getResources().openRawResourceFd(R.raw.beep2);
//      try {
//        mp.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
//      } catch (Exception e){e.printStackTrace();}
      mp2.start();
    }
  }
  
  public void freeResources(){
    mp1.stop();
    mp2.stop();
  }
  
}

About the student

Daniel Vu is a senior at Southside High School. He is the drum major of the Orange Express Marching Band and vice-president of the Physics Club at Southside. In his spare time, he enjoys playing various percussion instruments and playing with Rubik's Cubes.

 
Picture of the Student

Warning: include(footerMrRstudentsAndroid.htm): failed to open stream: No such file or directory in /home/intuitor/intuitor.com/student/Comp Sci I Objectives/AP Com Sci Q2/Android Phone Site/DanielDrum.php on line 971

Warning: include(): Failed opening 'footerMrRstudentsAndroid.htm' for inclusion (include_path='.:/usr/local/lib/php:/usr/local/php5/lib/pear') in /home/intuitor/intuitor.com/student/Comp Sci I Objectives/AP Com Sci Q2/Android Phone Site/DanielDrum.php on line 971