Update piano view from MIDI keyboard

This patch plumbs note on/off messages from the USB MIDI keyboard to
the piano view, so it's possible to see the notes being played.

Also contains some small UI improvements, like scaling some things to
the device density.
master
Raph Levien 11 years ago
parent 7a5706054e
commit cf96a0b192
  1. 46
      android/src/com/levien/synthesizer/android/service/SynthesizerService.java
  2. 41
      android/src/com/levien/synthesizer/android/ui/PianoActivity2.java
  3. 13
      android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java
  4. 2
      android/src/com/levien/synthesizer/android/widgets/piano/PianoKey.java
  5. 24
      android/src/com/levien/synthesizer/android/widgets/piano/PianoView.java

@ -86,15 +86,6 @@ public class SynthesizerService extends Service {
params.bufferSize = 64; params.bufferSize = 64;
androidGlue_ = new AndroidGlue(); androidGlue_ = new AndroidGlue();
midiListener_ = new MessageForwarder(androidGlue_) {
@Override
public void onController(int channel, int control, int value) {
super.onController(channel, control, value);
if (onCcListener_!= null) {
onCcListener_.onCcChange(channel, control, value);
}
}
};
androidGlue_.start(params.sampleRate, params.bufferSize); androidGlue_.start(params.sampleRate, params.bufferSize);
InputStream patchIs = getResources().openRawResource(R.raw.rom1a); InputStream patchIs = getResources().openRawResource(R.raw.rom1a);
byte[] patchData = new byte[4104]; byte[] patchData = new byte[4104];
@ -109,6 +100,31 @@ public class SynthesizerService extends Service {
Log.e(getClass().getName(), "loading patches failed"); Log.e(getClass().getName(), "loading patches failed");
} }
} }
midiListener_ = new MessageForwarder(androidGlue_) {
@Override
public void onController(int channel, int control, int value) {
super.onController(channel, control, value);
if (onCcListener_!= null) {
onCcListener_.onCcChange(channel, control, value);
}
}
@Override
public void onNoteOn(int channel, int note, int velocity) {
super.onNoteOn(channel, note, velocity);
if (onNoteOnListener_ != null) {
onNoteOnListener_.onNote(channel, note, velocity);
}
}
@Override
public void onNoteOff(int channel, int note, int velocity) {
super.onNoteOff(channel, note, velocity);
if (onNoteOffListener_ != null) {
onNoteOffListener_.onNote(channel, note, velocity);
}
}
};
androidGlue_.setPlayState(true); androidGlue_.setPlayState(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
@ -272,14 +288,24 @@ public class SynthesizerService extends Service {
} }
}; };
// There's a pretty good case to be made this should just tee a MidiListener instead
public interface OnCcListener { public interface OnCcListener {
abstract void onCcChange(int channel, int cc, int value); abstract void onCcChange(int channel, int cc, int value);
} }
public interface OnNoteListener {
abstract void onNote(int channel, int note, int velocity);
}
public void setOnCcListener(OnCcListener onCcListener) { public void setOnCcListener(OnCcListener onCcListener) {
onCcListener_ = onCcListener; onCcListener_ = onCcListener;
} }
public void setOnNoteListeners(OnNoteListener onNoteOn, OnNoteListener onNoteOff) {
onNoteOnListener_ = onNoteOn;
onNoteOffListener_ = onNoteOff;
}
private MidiListener midiListener_; private MidiListener midiListener_;
// Binder to use for Activities in this process. // Binder to use for Activities in this process.
@ -298,4 +324,6 @@ public class SynthesizerService extends Service {
// Plumbing for MIDI events // Plumbing for MIDI events
private OnCcListener onCcListener_; private OnCcListener onCcListener_;
private OnNoteListener onNoteOnListener_;
private OnNoteListener onNoteOffListener_;
} }

@ -72,8 +72,9 @@ public class PianoActivity2 extends Activity {
presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() { presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
synthesizerService_.getMidiListener().onProgramChange(0, position); if (synthesizerService_ != null) {
sendMidiBytes(new byte[] {(byte)0xc0, (byte)position}); synthesizerService_.getMidiListener().onProgramChange(0, position);
}
} }
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {
} }
@ -81,20 +82,27 @@ public class PianoActivity2 extends Activity {
cutoffKnob_.setKnobListener(new KnobListener() { cutoffKnob_.setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) { public void onKnobChanged(double newValue) {
int value = (int)Math.round(newValue * 127); // Maybe actually bind these at synthesizer service connection time?
synthesizerService_.getMidiListener().onController(0, 1, value); if (synthesizerService_ != null) {
int value = (int)Math.round(newValue * 127);
synthesizerService_.getMidiListener().onController(0, 1, value);
}
} }
}); });
resonanceKnob_.setKnobListener(new KnobListener() { resonanceKnob_.setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) { public void onKnobChanged(double newValue) {
int value = (int)Math.round(newValue * 127); if (synthesizerService_ != null) {
synthesizerService_.getMidiListener().onController(0, 2, value); int value = (int)Math.round(newValue * 127);
synthesizerService_.getMidiListener().onController(0, 2, value);
}
} }
}); });
overdriveKnob_.setKnobListener(new KnobListener() { overdriveKnob_.setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) { public void onKnobChanged(double newValue) {
int value = (int)Math.round(newValue * 127); if (synthesizerService_ != null) {
synthesizerService_.getMidiListener().onController(0, 3, value); int value = (int)Math.round(newValue * 127);
synthesizerService_.getMidiListener().onController(0, 3, value);
}
} }
}); });
@ -247,6 +255,23 @@ public class PianoActivity2 extends Activity {
}); });
} }
}); });
synthesizerService_.setOnNoteListeners(new SynthesizerService.OnNoteListener() {
public void onNote(final int channel, final int note, final int velocity) {
runOnUiThread(new Runnable() {
public void run() {
piano_.setNoteOn(note, true);
}
});
}
}, new SynthesizerService.OnNoteListener() {
public void onNote(final int channel, final int note, final int velocity) {
runOnUiThread(new Runnable() {
public void run() {
piano_.setNoteOn(note, false);
}
});
}
});
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
synthesizerService_ = null; synthesizerService_ = null;

@ -21,14 +21,14 @@ import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.RadialGradient; import android.graphics.RadialGradient;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.graphics.SweepGradient; import android.graphics.SweepGradient;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -52,11 +52,14 @@ public class KnobView extends View {
knobValue_ = a.getFloat(R.styleable.KnobView_value, 0.5f); knobValue_ = a.getFloat(R.styleable.KnobView_value, 0.5f);
min_ = a.getFloat(R.styleable.KnobView_min, 0.0f); min_ = a.getFloat(R.styleable.KnobView_min, 0.0f);
max_ = a.getFloat(R.styleable.KnobView_max, 1.0f); max_ = a.getFloat(R.styleable.KnobView_max, 1.0f);
a.recycle();
// Set up the drawing structures. // Set up the drawing structures.
knobPaint_ = new Paint(); knobPaint_ = new Paint();
knobPaint_.setAntiAlias(true); knobPaint_.setAntiAlias(true);
knobPaint_.setColor(Color.WHITE); knobPaint_.setColor(Color.WHITE);
float density = getResources().getDisplayMetrics().density;
knobPaint_.setStrokeWidth(2.0f * density);
rect_ = new Rect(); rect_ = new Rect();
rectF_ = new RectF(); rectF_ = new RectF();
textRect_ = new Rect(); textRect_ = new Rect();
@ -64,7 +67,8 @@ public class KnobView extends View {
// The listener has to be set later. // The listener has to be set later.
listener_ = null; listener_ = null;
setPadding(3, 3, 3, 3); int padding = (int)(3.0 * density + 0.5);
setPadding(padding, padding, padding, padding);
} }
/** /**
@ -262,7 +266,6 @@ public class KnobView extends View {
knobPaint_.setShader(null); knobPaint_.setShader(null);
knobPaint_.setColor(Color.WHITE); knobPaint_.setColor(Color.WHITE);
knobPaint_.setStyle(Style.STROKE); knobPaint_.setStyle(Style.STROKE);
knobPaint_.setStrokeWidth(4.0f);
final float arcWidth = 15.0f; final float arcWidth = 15.0f;
canvas.drawArc(rectF_, canvas.drawArc(rectF_,
(float)(knobValue_ * 360 * 0.8 + 90 - arcWidth / 2 + 36), (float)(knobValue_ * 360 * 0.8 + 90 - arcWidth / 2 + 36),

@ -44,6 +44,8 @@ public abstract class PianoKey {
fillPaint_.setStyle(Paint.Style.FILL); fillPaint_.setStyle(Paint.Style.FILL);
strokePaint_.setStyle(Paint.Style.STROKE); strokePaint_.setStyle(Paint.Style.STROKE);
strokePaint_.setColor(Color.BLACK); strokePaint_.setColor(Color.BLACK);
float strokeWidth = 1.0f * piano.getResources().getDisplayMetrics().density;
strokePaint_.setStrokeWidth(strokeWidth);
} }
/** /**

@ -47,6 +47,7 @@ public class PianoView extends View {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PianoView); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PianoView);
octaves_ = a.getInteger(R.styleable.PianoView_octaves, 1); octaves_ = a.getInteger(R.styleable.PianoView_octaves, 1);
firstOctave_ = a.getInteger(R.styleable.PianoView_first_octave, 4); firstOctave_ = a.getInteger(R.styleable.PianoView_first_octave, 4);
a.recycle();
// Set up basic drawing structs, just so we don't have to allocate this later when we draw. // Set up basic drawing structs, just so we don't have to allocate this later when we draw.
drawingRect_ = new Rect(); drawingRect_ = new Rect();
@ -408,6 +409,29 @@ public class PianoView extends View {
}); });
} }
public void setNoteOn(int note, boolean on) {
// This is somewhat painful, hopefully can be done better.
for (int i = 0; i < keys_.length; i++) {
PianoKey key = keys_[i];
if (key instanceof NotePianoKey) {
int midiNote = Note.getKeyforLog12TET(((NotePianoKey) key).getLogFrequency());
if (midiNote == note) {
boolean redraw;
if (on) {
// using the last finger is something of a hack
redraw = key.onTouchDown(FINGERS - 1);
} else {
redraw = key.onTouchUp(FINGERS - 1);
}
if (redraw) {
invalidate();
}
break;
}
}
}
}
// The most recent screen rect that this keyboard was drawn into. // The most recent screen rect that this keyboard was drawn into.
// //
// This is basically a stack variable for onDraw. It's a member variable only so that we can // This is basically a stack variable for onDraw. It's a member variable only so that we can

Loading…
Cancel
Save