Skip to content
November 7, 2010 / Daniel Freeman

Dr Android Answers: Music Editor

Could you advice how to set a canvas array to store the notes that user key in? I am trying to change my coding to the SurfaceView, and use the surface holder.

ChooChin


I liked your prototype, but the displayed musical scores were too small to accurately drag notes onto.  I suppose it depends how nimble your fingers are, but I’ve noticed that the HTC Android touch screen doesn’t feel as accurate as my Apple device.  So I think the editable music score area should be bigger than the displayed music.  I imagined panning along the music score, and selecting a portion to edit, which is rendered larger at the bottom of the screen.

I like SurfaceView, but in this case I think a normal View/invalidate() would be better.  The screen isn’t constantly animated.  Only when you edit notes.

In my implementation, I store the notes numerically in the mNotes array.  0 (zero) corresponds to f, 1 to e, 2 to d, and so on.  -1 corresponds to g.  Integer.MAX_VALUE means there is no note at that position.

Rather than dragging notes onto the score.  Just touch the score, and a note will appear at that position.  Then drag vertically into the right position.  Any note can be edited after it has been placed.  To remove a note drag up or down beyond the editable region.

 

package com.danielfreeman.musicalscore;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class EditBar extends View implements OnTouchListener {

	protected static final int BACKGROUND_COLOUR = Color.WHITE;

	protected static final int LINE_COLOUR = Color.BLACK;
	protected static final int NOTE_COLOUR_OFF = Color.DKGRAY;
	protected static final int NOTE_COLOUR_ON = Color.BLACK;

	protected static final float LINE_SPACING = 20.0f;
	protected static final float TOP = 128.0f;
	protected static final float MARGIN = 16.0f;

	protected static final int NOTES_IN_BAR = 10;
	protected static final int NOTE_LOW = -4;
	protected static final int NOTE_HIGH = 12;

	protected static final float NOTE_X = 3;
	protected static final float NOTE_Y = 6;
	protected static final float STALK_UP = -64;
	protected static final float STALK_DOWN = 68;
	protected static final float SEG = 16;
	protected static final int MID = 4;

	protected Paint mScorePaint = new Paint();
	protected Paint mNotePaint = new Paint();
	protected int [] mNotes = new int[NOTES_IN_BAR];
	protected boolean mDragging = false;
	protected int mLastIndex = -1;

	public EditBar(Context context) {
        super(context);
        for (int i = 0; i<NOTES_IN_BAR; i++) mNotes[i] = Integer.MAX_VALUE;
        initialiseColours();
    }

    @Override
    protected void onDraw(Canvas canvas) {
    	canvas.drawColor(BACKGROUND_COLOUR);
    	drawScore(canvas);
    	drawNotes(canvas);
    }

    protected void initialiseColours() {
    	mScorePaint.setColor(LINE_COLOUR);
    	mScorePaint.setStyle(Style.FILL_AND_STROKE);
    	mNotePaint.setColor(NOTE_COLOUR_OFF);
    	mNotePaint.setStyle(Style.FILL_AND_STROKE);
    }

    protected void drawScore(Canvas canvas) {
    	for (int i=0; i<5; i++) {
    		float y = TOP + i * LINE_SPACING;
    		canvas.drawRect(MARGIN, y, getWidth()-MARGIN, y+1, mScorePaint);
    	}
    }

    protected void drawNotes(Canvas canvas) {
    	 for (int i = 0; i<NOTES_IN_BAR; i++) {
    		 int position = mNotes[i];
    		 if (position!=Integer.MAX_VALUE) {
    			 drawQuarterNote(canvas, i, position);
    		 }
    	 }
    }

    protected void drawQuarterNote(Canvas canvas, int index, int position) {
    	float x = indexToX(index);
    	float y = positionToY(position);
    	if (position<0 || position>9) {
    		float y0 = positionToY(2*(position/2));
    		canvas.drawRect(x - SEG, y0, x + SEG, y0+1, mNotePaint);
    	}
    	Path path = new Path();
    	path.moveTo(x + 3*NOTE_X, y - NOTE_Y);
    	path.cubicTo(x + 5*NOTE_X, y, x-NOTE_X, y+2*NOTE_Y, x-3*NOTE_X, y+NOTE_Y);
    	path.cubicTo(x - 5*NOTE_X, y, x+NOTE_X, y-2*NOTE_Y, x + 3*NOTE_X, y-NOTE_Y);
    	canvas.drawPath(path, mNotePaint);
    	canvas.drawRect(x + 3*NOTE_X - 1, y - NOTE_Y/2, x + 3*NOTE_X + 1 , y - NOTE_Y/2 + ((position > MID ) ? STALK_UP : STALK_DOWN), mNotePaint);
    }

    protected float indexToX(int index) {
    	return MARGIN + (index + 0.5f)*(getWidth()-2f*MARGIN)/NOTES_IN_BAR;
    }

    protected float positionToY(int position) {
    	return TOP + position * LINE_SPACING / 2f;
    }

    protected int xToIndex(float x) {
    	return (int)Math.floor((x-MARGIN)/((getWidth()-2f*MARGIN)/NOTES_IN_BAR));
    }

    protected int yToPosition(float y) {
    	return (int)Math.floor((y-TOP)/(LINE_SPACING/2));
    }

    protected void editNote(float x, float y) {
    	int index = xToIndex(x);
    	if (index>=0 && index<NOTES_IN_BAR) {
    		if (mLastIndex!=index) mDragging = false;
    		int position = yToPosition(y);
    		int note = mNotes[index];
    		if (note == Integer.MAX_VALUE || note == position) mDragging = true;
    		if (mDragging) {
    			if (position>=NOTE_LOW && position<=NOTE_HIGH) mNotes[index] = position;
    			else mNotes[index] = Integer.MAX_VALUE;
    			invalidate();
    		}
    		mLastIndex = index;
    	}

    }

    public boolean onTouch(View view, MotionEvent event) {
    	return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        editNote(event.getX(), event.getY());
        return true;
    }

}

The main program is as you’d expect.

package com.danielfreeman.musicalscore;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;

public class MusicalScore extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        EditBar editBar = new EditBar(this);
        setContentView(editBar);
    }
}

Note, there seems to be a problem with displaying the graphics properly in the 1.5 emulator. The 2.2 emulator works ok.

I’ve put the following attribute into the application tag of the manifest file to make the application full screen.

android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”

Advertisements

To discuss MadComponents/MC3D, join the Facebook group!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: