[musikcube] r378 committed - Started on a PaceDetector.

0 views
Skip to first unread message

codesite...@google.com

unread,
Aug 16, 2009, 6:27:14 PM8/16/09
to musikc...@googlegroups.com
Revision: 378
Author: onnerby
Date: Sun Aug 16 15:27:03 2009
Log: Started on a PaceDetector.
http://code.google.com/p/musikcube/source/detail?r=378

Added:
/trunk/src/android/src/org/musikcube/BPMControl.java
/trunk/src/android/src/org/musikcube/core/PaceDetector.java
Modified:
/trunk/src/android/AndroidManifest.xml
/trunk/src/android/res/layout/main.xml
/trunk/src/android/src/org/musikcube/Service.java
/trunk/src/android/src/org/musikcube/main.java

=======================================
--- /dev/null
+++ /trunk/src/android/src/org/musikcube/BPMControl.java Sun Aug 16
15:27:03 2009
@@ -0,0 +1,249 @@
+package org.musikcube;
+
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import org.musikcube.core.Library;
+import org.musikcube.core.Player;
+import org.musikcube.core.Track;
+import org.musikcube.core.Player.OnTrackUpdateListener;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class BPMControl extends Activity implements OnTrackUpdateListener {
+
+ private Track track = new Track();
+ private int duration = 0;
+ private Object lock = new Object();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.v("MC2::PC","OnCreate");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.play_control);
+/*
+ ImageButton nextButton = (ImageButton)findViewById(R.id.MediaNext);
+ nextButton.setOnClickListener(this.onNextClick);
+ ImageButton pauseButton =
(ImageButton)findViewById(R.id.MediaPause);
+ pauseButton.setOnClickListener(this.onPauseClick);
+ */
+
this.callbackTrackPositionsUpdateHandler.postDelayed(callbackTrackPositionsUpdateRunnable,500);
+ }
+/*
+ private OnClickListener onNextClick = new OnClickListener() {
+ public void onClick(View v){
+ Intent intent = new Intent(PlayerControl.this,
org.musikcube.Service.class);
+ intent.putExtra("org.musikcube.Service.action", "next");
+ startService(intent);
+ }
+ };
+ private OnClickListener onPauseClick = new OnClickListener() {
+ public void onClick(View v){
+ Intent intent = new Intent(PlayerControl.this,
org.musikcube.Service.class);
+ intent.putExtra("org.musikcube.Service.action", "stop");
+ startService(intent);
+ }
+ };
+*/
+ public void OnTrackBufferUpdate(int percent) {
+ synchronized(lock){
+ }
+
this.callbackTrackPositionsUpdateHandler.post(this.callbackTrackPositionsUpdateRunnable);
+ }
+ public void OnTrackPositionUpdate(int secondsPlayed) {
+ synchronized(lock){
+ }
+
this.callbackTrackPositionsUpdateHandler.post(this.callbackTrackPositionsUpdateRunnable);
+ }
+ public void OnTrackUpdate() {
+ this.callbackTrackUpdateHandler.post(this.callbackTrackUpdateRunnable);
+ }
+
+
+ @Override
+ protected void onPause() {
+ Log.v("MC2::PC","OnPause");
+ Player.GetInstance().SetUpdateListener(null);
+ super.onPause();
+ }
+ @Override
+ protected void onResume() {
+ Log.v("MC2::PC","OnResume");
+ Player.GetInstance().SetUpdateListener(this);
+ super.onResume();
+ }
+
+ // Need handler for callbacks to the UI thread
+ final Handler callbackTrackUpdateHandler = new Handler();
+ // Create runnable for posting
+ final Runnable callbackTrackUpdateRunnable = new Runnable() {
+ public void run() {
+ OnUpdateTrackUI();
+ }
+ };
+
+ public void OnUpdateTrackUI() {
+ TextView titleView = (TextView)findViewById(R.id.TrackTitle);
+ TextView albumView = (TextView)findViewById(R.id.TrackAlbum);
+ TextView artistView = (TextView)findViewById(R.id.TrackArtist);
+ TextView durationView = (TextView)findViewById(R.id.TrackDuration);
+
+ int thumbnailId = 0;
+
+ synchronized(lock){
+
+ this.track = Player.GetInstance().GetCurrentTrack();
+ if(this.track==null){
+ this.track = new Track();
+ }
+
+ String thumbnailString = this.track.metadata.get("thumbnail_id");
+ if(thumbnailString!=null){
+ thumbnailId = Integer.parseInt(thumbnailString);
+ }
+
+ String title = this.track.metadata.get("title");
+ if(title==null){
+ titleView.setText("Title:");
+ }else{
+ titleView.setText("Title: "+title);
+ }
+ String album = this.track.metadata.get("album");
+ if(album==null){
+ albumView.setText("Album:");
+ }else{
+ albumView.setText("Album: "+album);
+ }
+ String artist = this.track.metadata.get("visual_artist");
+ if(artist==null){
+ artistView.setText("Artist:");
+ }else{
+ artistView.setText("Artist: "+artist);
+ }
+
+ String duration = this.track.metadata.get("duration");
+ if(duration==null){
+ this.duration = 0;
+ }else{
+ this.duration = Integer.parseInt(duration);
+ }
+ int minutes = (int)Math.floor(this.duration/60);
+ int seconds = this.duration-minutes*60;
+ String durationText = Integer.toString(minutes)+":";
+ if(seconds<10){ durationText += "0"; }
+ durationText += Integer.toString(seconds);
+ durationView.setText(durationText);
+ }
+
+ // clear image
+ ImageView cover = (ImageView)findViewById(R.id.AlbumCover);
+ cover.setImageResource(R.drawable.album);
+
+ if(thumbnailId!=0){
+ // Load image
+ Library library = Library.GetInstance();
+ new
DownloadAlbumCoverTask().execute("http://"+library.host+":"+library.httpPort+"/cover/?cover_id="+thumbnailId);
+ }
+
+ }
+
+ private class DownloadAlbumCoverTask extends
AsyncTask<String,Integer,Bitmap>{
+
+ protected Bitmap doInBackground(String... params) {
+ try {
+ URL url = new URL(params[0]);
+ HttpURLConnection conn= (HttpURLConnection)url.openConnection();
+ conn.setDoInput(true);
+ conn.connect();
+ //int length = conn.getContentLength();
+ InputStream is = conn.getInputStream();
+ Bitmap bm = BitmapFactory.decodeStream(is);
+ return bm;
+ } catch (Exception e) {
+ Log.v("mC2:PLAYER","Error "+e.getMessage());
+// e.printStackTrace();
+ return null;
+ }
+ }
+
+ protected void onPostExecute(Bitmap result){
+ if(result==null){
+ }else{
+ ImageView cover = (ImageView)findViewById(R.id.AlbumCover);
+ cover.setImageBitmap(result);
+ }
+ }
+ }
+
+ // Need handler for callbacks to the UI thread
+ final Handler callbackTrackPositionsUpdateHandler = new Handler();
+ // Create runnable for posting
+ final Runnable callbackTrackPositionsUpdateRunnable = new Runnable() {
+ public void run() {
+ OnUpdateTrackPositionsUI();
+ }
+ };
+
+ public void OnUpdateTrackPositionsUI() {
+ int msPosition = Player.GetInstance().GetTrackPosition();
+ int position = msPosition/1000;
+ int minutes = (int)Math.floor(position/60);
+ int seconds = position-minutes*60;
+ String positionText = Integer.toString(minutes)+":";
+ if(seconds<10){ positionText += "0"; }
+ positionText += Integer.toString(seconds);
+ TextView positionView = (TextView)findViewById(R.id.TrackPosition);
+ positionView.setText(positionText);
+
+ SeekBar seekBar = (SeekBar)findViewById(R.id.TrackProgress);
+ synchronized (this.lock) {
+ if(this.duration==0){
+ seekBar.setProgress(0);
+ }else{
+ seekBar.setProgress(msPosition/this.duration);
+ }
+ seekBar.setSecondaryProgress(10*Player.GetInstance().GetTrackBuffer());
+ }
+
+ // Next callback in 0.5 seconds
+
this.callbackTrackPositionsUpdateHandler.postDelayed(callbackTrackPositionsUpdateRunnable,500);
+
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.default_menu, menu);
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+
//Log.i("MC2.onContextItemSelected","item "+item.getItemId()+" "+R.id.context_settings);
+ switch (item.getItemId()) {
+ case R.id.context_settings:
+ startActivity(new Intent(this, org.musikcube.Preferences.class));
+ return true;
+ case R.id.context_browse:
+ startActivity(new Intent(this, org.musikcube.main.class));
+ return true;
+ case R.id.context_controls:
+ startActivity(new Intent(this, org.musikcube.PlayerControl.class));
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/android/src/org/musikcube/core/PaceDetector.java Sun Aug 16
15:27:03 2009
@@ -0,0 +1,169 @@
+package org.musikcube.core;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+
+public class PaceDetector implements Runnable, SensorEventListener {
+
+ static public float MAX_BPM = 165;
+ static public float MIN_BPM = 80;
+ static public int WAVE_MEMORY = 12;
+ static public int WAVE_MIN_CALC = 6;
+ static public float WAVE_MIN_BPM_DIFF = 80; // This is in miliseconds
+ static public int WAVE_COMPARE = 3;
+
+ private float currentBPM = 0;
+ private float currentAccuracy = 0;
+
+ private class PaceDimension{
+ public java.util.ArrayList<Long> beatTimes = new
java.util.ArrayList<Long>();
+ public java.util.ArrayList<Float> amplitude = new
java.util.ArrayList<Float>();
+
+ private float lastValue = 0;
+ private float lastDiff = 0;
+ private float currentMax = 0;
+ private float currentMin = 0;
+
+ public float currentBPM = 0;
+ public float currentAccuracy = 0;
+
+ final public void NextValue(float value){
+ float diff = value-this.lastValue;
+
+ if(value<this.currentMin){
+ this.currentMin = value;
+ }
+ if(value>this.currentMax){
+ this.currentMax = value;
+ }
+
+ if(this.lastDiff>=0 && diff<0){
+ // this is a top on the curve
+ this.beatTimes.add(android.os.SystemClock.elapsedRealtime());
+ this.amplitude.add(this.currentMax-this.currentMin);
+
+ // Reset the amplitude
+ this.currentMin = value;
+ this.currentMax = value;
+
+ // only keep the last 10 waves
+ while(this.beatTimes.size()>WAVE_MEMORY){
+ this.beatTimes.remove(0);
+ this.amplitude.remove(0);
+ }
+
+ // Lets calculate BPM
+ long bpmSum = 0;
+ java.util.TreeSet<Long> bpms = new java.util.TreeSet<Long>();
+ for(int i=0;i<this.beatTimes.size()-WAVE_COMPARE;i++){
+ long orgSample = this.beatTimes.get(i);
+ for(int j=i+1;j<i+WAVE_COMPARE;j++){
+ //float bpmSample = 60000/(this.beatTimes.get(j)-orgSample);
+ long bpmSample = this.beatTimes.get(j)-orgSample;
+ if(bpmSample>(60000/MAX_BPM) && bpmSample<(60000/MIN_BPM)){
+ bpms.add(bpmSample);
+ bpmSum += bpmSample;
+ }
+ }
+ }
+
+ // Lets remove the most "off" samples and correct the AVG until we are
down to the desired "diff"
+ boolean qualified = false;
+ long bpmDiff = 0;
+
+ while(!qualified && bpms.size()>=WAVE_MIN_CALC){
+ Long first = bpms.first();
+ Long last = bpms.last();
+ bpmDiff = last-first;
+ int bpmSize = bpms.size();
+
+// Log.v("MC2::DIFF","diff "+bpmSize+" "+first+"-"+last+" diff="+bpmDiff);
+
+ if(bpmDiff<WAVE_MIN_BPM_DIFF){
+ qualified = true;
+ }else{
+ // Remove the element that is most far away from the average
+ long avg = bpmSum/bpmSize;
+ if(avg-first>last-avg){
+ // Remove first
+ bpmSum -= first;
+ bpms.remove(first);
+ }else{
+ // Remove last
+ bpmSum -= last;
+ bpms.remove(last);
+ }
+ }
+ }
+
+ if(qualified){
+ // Get avg amplitude
+ float amplitude = this.amplitude.get(0);
+ for(int i=1;i<this.amplitude.size();i++){
+ amplitude+=this.amplitude.get(i);
+ }
+ amplitude /= this.amplitude.size();
+
+
+ this.currentBPM = (60000*bpms.size())/bpmSum;
+ this.currentAccuracy = (100-bpmDiff)+bpms.size()*13+amplitude*5;
+ PaceDetector.this.ChangeBPM(this.currentBPM,this.currentAccuracy);
+ }
+ }
+ this.lastDiff = diff;
+ this.lastValue = value;
+ }
+
+ }
+
+ private PaceDimension xAxis = new PaceDimension();
+ private PaceDimension yAxis = new PaceDimension();
+ private PaceDimension zAxis = new PaceDimension();
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+
+ public void onSensorChanged(SensorEvent event) {
+ float values[] = event.values;
+ this.xAxis.NextValue(values[0]);
+ this.yAxis.NextValue(values[1]);
+ this.zAxis.NextValue(values[2]);
+// Log.v("MC2::TJOOOO",""+values[0]);
+
+// Log.v("mC2::ACC","x="+values[0]+" y="+values[1]+" z="+values[2]);
+ }
+
+ public void ChangeBPM(float bpm,float accuracy){
+ if(accuracy>=this.xAxis.currentAccuracy ||
accuracy>=this.yAxis.currentAccuracy ||
accuracy>=this.zAxis.currentAccuracy){
+ this.currentBPM = bpm;
+ this.currentAccuracy = accuracy;
+ Log.v("MC2::BPM","bpm="+bpm+" "+accuracy);
+ }
+ }
+
+ public void StartSensor(Context context){
+ Log.v("mC2::ACC","Sensor");
+ SensorManager sensorMgr =
(SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ Sensor accelerometer =
sensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+
sensorMgr.registerListener(this,accelerometer,SensorManager.SENSOR_DELAY_GAME);
+ }
+
+ public void StopSensor(Context context){
+ SensorManager sensorMgr =
(SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ sensorMgr.unregisterListener(this);
+ }
+
+
+
+ public void run() {
+ // TODO Auto-generated method stub
+
+ }
+
+}
=======================================
--- /trunk/src/android/AndroidManifest.xml Tue Aug 11 15:17:53 2009
+++ /trunk/src/android/AndroidManifest.xml Sun Aug 16 15:27:03 2009
@@ -3,7 +3,7 @@
package="org.musikcube"
android:versionCode="1"
android:versionName="1.0">
- <application android:icon="@drawable/icon"
android:label="@string/app_name" android:name="App">
+ <application android:icon="@drawable/icon"
android:label="@string/app_name" android:name="App"
android:debuggable="true">
<activity android:name=".main"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
@@ -25,4 +25,5 @@

+
</manifest>
=======================================
--- /trunk/src/android/res/layout/main.xml Sun Aug 9 13:57:19 2009
+++ /trunk/src/android/res/layout/main.xml Sun Aug 16 15:27:03 2009
@@ -13,7 +13,7 @@
<TextView android:id="@+id/TextView01"
android:layout_height="wrap_content" android:text="Browse by:"
android:layout_width="fill_parent"
android:background="@color/headlineColor" android:textSize="16sp"
android:padding="4sp"></TextView>
<Button android:layout_height="wrap_content"
android:layout_width="fill_parent" android:textSize="22sp"
android:text="Genre" android:id="@+id/GenresButton"></Button>
<Button android:layout_height="wrap_content"
android:layout_width="fill_parent" android:textSize="22sp"
android:id="@+id/ArtistsButton" android:text="Artist"></Button>
-
+ <Button android:layout_height="wrap_content"
android:layout_width="fill_parent" android:textSize="22sp"
android:id="@+id/BPMButton" android:text="Workout mode"></Button>
</LinearLayout>

<TextView android:id="@+id/StatusView" android:text="Status: -"
android:layout_width="fill_parent"
android:background="@color/headlineColor" android:textSize="16sp"
android:padding="4sp" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_weight="0.1"></TextView>
=======================================
--- /trunk/src/android/src/org/musikcube/Service.java Tue Aug 11 15:17:53
2009
+++ /trunk/src/android/src/org/musikcube/Service.java Sun Aug 16 15:27:03
2009
@@ -4,6 +4,7 @@
package org.musikcube;

import org.musikcube.core.Library;
+import org.musikcube.core.PaceDetector;
import org.musikcube.core.Player;
import org.musikcube.core.Track;

@@ -23,6 +24,7 @@
Library library;
Player player;
boolean showingNotification = false;
+ PaceDetector paceDetector;

/**
*
@@ -82,6 +84,13 @@
//Log.i("musikcube::Service","Shutdown");
this.stopSelf();
}
+ if(action.equals("bpmstart")){
+ if(this.paceDetector==null){
+ this.paceDetector = new PaceDetector();
+ this.paceDetector.StartSensor(this);
+ }
+ }
+
if(action.equals("player_start")){
Track track = Player.GetInstance().GetCurrentTrack();

=======================================
--- /trunk/src/android/src/org/musikcube/main.java Sun Aug 9 13:57:19 2009
+++ /trunk/src/android/src/org/musikcube/main.java Sun Aug 16 15:27:03 2009
@@ -27,36 +27,43 @@

Button genreButton = (Button)findViewById(R.id.GenresButton);
genreButton.setOnClickListener(this.onGenreClick);
-
genreButton.setEnabled(false);

Button artistsButton = (Button)findViewById(R.id.ArtistsButton);
artistsButton.setOnClickListener(this.onArtistsClick);
-
artistsButton.setEnabled(false);
+
+ Button bpmButton = (Button)findViewById(R.id.BPMButton);
+ bpmButton.setOnClickListener(this.onBPMClick);
+ bpmButton.setEnabled(false);

}

private OnClickListener onGenreClick = new OnClickListener() {
-
public void onClick(View v){
Intent intent = new Intent(main.this, CategoryList.class);

intent.putExtra("org.musikcube.CategoryList.listCategory", "genre,artist,album");
startActivity(intent);
-
- }
-
+ }
};

private OnClickListener onArtistsClick = new OnClickListener() {
-
public void onClick(View v){
Intent intent = new Intent(main.this, CategoryList.class);

intent.putExtra("org.musikcube.CategoryList.listCategory", "artist,album");
startActivity(intent);
-
- }
-
+ }
+ };
+
+ private OnClickListener onBPMClick = new OnClickListener() {
+ public void onClick(View v){
+ Intent intent = new Intent(main.this, org.musikcube.Service.class);
+ intent.putExtra("org.musikcube.Service.action", "bpmstart");
+ startService(intent);
+
+ Intent intent2 = new Intent(main.this, PlayerControl.class);
+ startActivity(intent2);
+ }
};

public boolean onCreateOptionsMenu(Menu menu) {
@@ -114,13 +121,16 @@
int status = Library.GetInstance().GetStatus();
Button genreButton = (Button)findViewById(R.id.GenresButton);
Button artistsButton = (Button)findViewById(R.id.ArtistsButton);
+ Button bpmButton = (Button)findViewById(R.id.BPMButton);

if(status==Library.STATUS_CONNECTED){
genreButton.setEnabled(true);
artistsButton.setEnabled(true);
+ bpmButton.setEnabled(true);
}else{
genreButton.setEnabled(false);
artistsButton.setEnabled(false);
+ bpmButton.setEnabled(false);
}

TextView statusText = (TextView)findViewById(R.id.StatusView);

Reply all
Reply to author
Forward
0 new messages