Mr

 Mr. Rogers' IB Computer Science - Google 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
Quick Summary


Title
Rhythm Notater

Status
WIP

Date Last Worked

11/19/10

Purpose
To transcribe rhythms shaken into the device.

Features
  • Functional Metronome

  • Uses the accelerometer

Possible Features

  • 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.

Unfinished Code
When the user clicks notate after the metronome is started, it will transcribe the rhythms onto the paintholder. Clicking erase will clear the SQLite Database.

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




Screenshots
Screen 1


Code

Main

package nr.co.dv297.rhythmnotater;

import android.app.Activity;
import android.content.ContentValues;
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 = (Button) findViewById(R.id.startbutton);
add_button = (Button) findViewById(R.id.add_button);
subtract_button = (Button) findViewById(R.id.subtract_button);
erase_button = (Button) findViewById(R.id.erase_button);
notate_button = (Button) findViewById(R.id.notate_button);
bpmvalue = (TextView) findViewById(R.id.bpmvalue);
parentcontainer = (LinearLayout) findViewById(R.id.parentcontainer);
paintcontainer = (LinearLayout) findViewById(R.id.paintcontainer);

textview01 = (TextView) findViewById(R.id.TextView01);
textview02 = (TextView) findViewById(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);
running = true;
met.startTimer();
shaker = new Shaker(this);
db = (new MyDBOpenHelper(getBaseContext())).getWritableDatabase();

}
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();
}
}

/**
* 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;}
}

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 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 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 = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
sm = new SoundManager(a);

}

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

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

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

if (tapOffMode) {
if(currentTap == 1 || currentTap == 3 || currentTap == 5 || currentTap == 6 || currentTap == 7 || 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((String) Main.getBpmValue()
.getText());
tpb = (60 / beats_per_minute) * 1000;
time_per_beat = (int) tpb;
total_time = 33 * 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;

}
}

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) {
// TODO Auto-generated method stub
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);
}
}

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 = { 0, 0, 0 }; // Refer to the x, y, and z acceleration of the device
private long ts1, ts2; // Used to calculate the time between shake events.

public Shaker(Activity a) {
sensorManager = (SensorManager) a.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.addDBItem(difference);

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 mp = 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);
mp = MediaPlayer.create(c, R.raw.beep1);
}

public void preparePlayer(){
try {
mp.reset();
mp.prepare();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
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 (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mp.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 (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mp.start();
}
}

public void cancel(){
mp.stop();
}

}