From e2f12383ae6887c0e65d302f0a5e0aa70cade0da Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Dec 2013 23:49:56 -0800 Subject: [PATCH] Crude touch processing for keyboard view This patch adds crude touch processing (non multitouch, no tracking), but it's enough to noodle out melodies to validate the new approach. The patch also wires up the new view and starts to make some style changes (holo light theme). --- android/AndroidManifest.xml | 3 +- android/res/layout/piano2.xml | 11 +-- .../android/service/SynthesizerService.java | 6 ++ .../android/ui/PianoActivity2.java | 16 ++-- .../widgets/keyboard/KeyboardView.java | 73 ++++++++++++++++++- 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3cb756a..66214a3 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -10,7 +10,8 @@ - + - - - diff --git a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java index e7ea3b3..e17c3b6 100755 --- a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java +++ b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java @@ -264,6 +264,12 @@ public class SynthesizerService extends Service { } }; + /** + * Set a MidiListener. At the moment, this listener gets all MIDI events, but + * it might change to only get them from the USB MIDI device. + * + * @param target MidiListener to receive messages, or null if none + */ public void setMidiListener(MidiListener target) { midiListener_.setSecondTarget(target); } diff --git a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java index 8e15010..2d6edec 100644 --- a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java @@ -42,6 +42,8 @@ import android.widget.Spinner; import com.levien.synthesizer.R; import com.levien.synthesizer.android.service.SynthesizerService; +import com.levien.synthesizer.android.widgets.keyboard.KeyboardSpec; +import com.levien.synthesizer.android.widgets.keyboard.KeyboardView; import com.levien.synthesizer.android.widgets.knob.KnobListener; import com.levien.synthesizer.android.widgets.knob.KnobView; import com.levien.synthesizer.android.widgets.piano.PianoView; @@ -62,7 +64,9 @@ public class PianoActivity2 extends Activity { WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.piano2); - piano_ = (PianoView)findViewById(R.id.piano); + //piano_ = (PianoView)findViewById(R.id.piano); + keyboard_ = (KeyboardView)findViewById(R.id.piano); + keyboard_.setKeyboardSpec(KeyboardSpec.make3Layer()); cutoffKnob_ = (KnobView)findViewById(R.id.cutoffKnob); resonanceKnob_ = (KnobView)findViewById(R.id.resonanceKnob); overdriveKnob_ = (KnobView)findViewById(R.id.overdriveKnob); @@ -183,7 +187,8 @@ public class PianoActivity2 extends Activity { @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private void onSynthConnected() { final MidiListener synthMidi = synthesizerService_.getMidiListener(); - piano_.bindTo(synthMidi); + //piano_.bindTo(synthMidi); + keyboard_.setMidiListener(synthMidi); cutoffKnob_.setKnobListener(new KnobListener() { public void onKnobChanged(double newValue) { @@ -209,7 +214,7 @@ public class PianoActivity2 extends Activity { public void onNoteOn(final int channel, final int note, final int velocity) { runOnUiThread(new Runnable() { public void run() { - piano_.setNoteOn(note, true); + keyboard_.onNote(note, velocity); } }); } @@ -217,7 +222,7 @@ public class PianoActivity2 extends Activity { public void onNoteOff(final int channel, final int note, final int velocity) { runOnUiThread(new Runnable() { public void run() { - piano_.setNoteOn(note, false); + keyboard_.onNote(note, 0); } }); } @@ -277,7 +282,8 @@ public class PianoActivity2 extends Activity { private SynthesizerService synthesizerService_; - private PianoView piano_; + //private PianoView piano_; + private KeyboardView keyboard_; private KnobView cutoffKnob_; private KnobView resonanceKnob_; private KnobView overdriveKnob_; diff --git a/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java index b1c5421..f7b3be4 100644 --- a/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java +++ b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java @@ -24,12 +24,15 @@ import android.graphics.Paint.Style; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; +import android.view.MotionEvent; import android.view.View; +import com.levien.synthesizer.core.midi.MidiListener; + public class KeyboardView extends View { public KeyboardView(Context context, AttributeSet attrs) { super(context, attrs); - nKeys_ = 24; // TODO: make configurable + nKeys_ = 36; // TODO: make configurable firstKey_ = 48; noteStatus_ = new byte[128]; drawingRect_ = new Rect(); @@ -46,6 +49,10 @@ public class KeyboardView extends View { invalidate(); } + public void setMidiListener(MidiListener listener) { + midiListener_ = listener; + } + public void onNote(int note, int velocity) { if (note >= 0 && note < 128) { noteStatus_[note] = (byte)velocity; @@ -63,7 +70,6 @@ public class KeyboardView extends View { float y0 = drawingRect_.top + strokeWidth_ * 0.5f; for (int i = 0; i < nKeys_; i++) { KeySpec ks = keyboardSpec_.keys[i % keyboardSpec_.keys.length]; - Log.d("synth", "ks["+i+"] = " + ks); float x = x0 + ((i / keyboardSpec_.keys.length) * keyboardSpec_.repeatWidth + ks.rect.left) * xscale; float y = y0 + ks.rect.top * yscale; @@ -89,6 +95,7 @@ public class KeyboardView extends View { paint_.setStyle(Style.STROKE); canvas.drawRect(x, y, x + width + strokeWidth_, y + height + strokeWidth_, paint_); } + // TODO: draw optional note text } } @@ -133,6 +140,68 @@ public class KeyboardView extends View { setMeasuredDimension(width, height); } + @Override + public boolean onTouchEvent(MotionEvent event) { + int action = event.getAction(); + int actionCode = action & MotionEvent.ACTION_MASK; + boolean redraw = false; + if (actionCode == MotionEvent.ACTION_DOWN) { + int pointerId = event.getPointerId(0); + float x = event.getX(); + float y = event.getY(); + float pressure = event.getPressure(); + redraw |= onTouchDown(pointerId, x, y, pressure); + } else if (actionCode == MotionEvent.ACTION_UP) { + int pointerId = event.getPointerId(0); + float x = event.getX(); + float y = event.getY(); + float pressure = event.getPressure(); + redraw |= onTouchUp(pointerId, x, y, pressure); + } + if (redraw) { + invalidate(); + } + return true; + } + + private int hitTest(float x, float y) { + /* convert x and y to KeyboardSpec space */ + float xscale = (drawingRect_.width() - strokeWidth_) * keyboardScale_; + float yscale = (drawingRect_.height() - strokeWidth_) / keyboardSpec_.height; + float xk = (x - 0.5f * strokeWidth_) / xscale; + float yk = (y - 0.5f * strokeWidth_) / yscale; + for (int i = 0; i < nKeys_; i++) { + KeySpec ks = keyboardSpec_.keys[i % keyboardSpec_.keys.length]; + float kx0 = ks.rect.left + (i / keyboardSpec_.keys.length) * keyboardSpec_.repeatWidth; + if (xk >= kx0 && xk < kx0 + ks.rect.width() && + yk >= ks.rect.top && yk < ks.rect.bottom) { + return i + firstKey_; + } + } + return -1; + } + + private boolean onTouchDown(int id, float x, float y, float pressure) { + int note = hitTest(x, y); + if (note >= 0) { + if (midiListener_ != null) { + midiListener_.onNoteOn(0, note, 64); + } + } + return false; + } + + private boolean onTouchUp(int id, float x, float y, float pressure) { + int note = hitTest(x, y); + if (note >= 0) { + if (midiListener_ != null) { + midiListener_.onNoteOff(0, note, 64); + } + } + return false; + } + private MidiListener midiListener_; + private Rect drawingRect_; private Paint paint_; private float strokeWidth_;