From 7a5706054e2dc4c860b748b7a83d8370f23aeb92 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 24 Dec 2013 23:53:13 -0800 Subject: [PATCH] Update controller views from MIDI keyboard This patch adds plumbing so that changing the controller values from a USB MIDI keyboard updates the knob views as well. --- .../android/service/SynthesizerService.java | 27 ++++++++- .../android/ui/PianoActivity2.java | 22 +++++++ .../android/widgets/knob/KnobView.java | 59 +++++++++---------- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java index b9c85dc..88f5f50 100755 --- a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java +++ b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java @@ -41,6 +41,7 @@ import android.util.Log; import com.levien.synthesizer.R; import com.levien.synthesizer.android.AndroidGlue; import com.levien.synthesizer.android.usb.UsbMidiDevice; +import com.levien.synthesizer.core.midi.MessageForwarder; import com.levien.synthesizer.core.midi.MidiListener; import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer; @@ -85,6 +86,15 @@ public class SynthesizerService extends Service { params.bufferSize = 64; 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); InputStream patchIs = getResources().openRawResource(R.raw.rom1a); byte[] patchData = new byte[4104]; @@ -129,7 +139,7 @@ public class SynthesizerService extends Service { } public MidiListener getMidiListener() { - return androidGlue_; + return midiListener_; } /** @@ -214,7 +224,7 @@ public class SynthesizerService extends Service { usbDevice_ = device; usbMidiConnection_ = connection; usbMidiInterface_ = intf; - usbMidiDevice_ = new UsbMidiDevice(androidGlue_, usbMidiConnection_, intf); + usbMidiDevice_ = new UsbMidiDevice(midiListener_, usbMidiConnection_, intf); usbMidiDevice_.start(); return true; } else { @@ -262,6 +272,16 @@ public class SynthesizerService extends Service { } }; + public interface OnCcListener { + abstract void onCcChange(int channel, int cc, int value); + } + + public void setOnCcListener(OnCcListener onCcListener) { + onCcListener_ = onCcListener; + } + + private MidiListener midiListener_; + // Binder to use for Activities in this process. private final IBinder binder_ = new LocalBinder(); @@ -275,4 +295,7 @@ public class SynthesizerService extends Service { private UsbMidiDevice usbMidiDevice_; private UsbInterface usbMidiInterface_; private UsbDevice usbDeviceNeedsPermission_; + + // Plumbing for MIDI events + private OnCcListener onCcListener_; } diff --git a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java index 5434aaa..dbf5c95 100644 --- a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java @@ -205,11 +205,16 @@ public class PianoActivity2 extends Activity { SynthesizerService.LocalBinder binder = (SynthesizerService.LocalBinder)service; synthesizerService_ = binder.getService(); piano_.bindTo(synthesizerService_.getMidiListener()); + + // Populate patch names (note: we could update an existing list rather than + // creating a new adapter, but it probably wouldn't save all that much). List patchNames = synthesizerService_.getPatchNames(); ArrayAdapter adapter = new ArrayAdapter( PianoActivity2.this, android.R.layout.simple_spinner_item, patchNames); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); presetSpinner_.setAdapter(adapter); + + // Handle any pending USB device events if (usbDevicePending_ != null) { synthesizerService_.connectUsbMidi(usbDevicePending_); usbDevicePending_ = null; @@ -225,6 +230,23 @@ public class PianoActivity2 extends Activity { } } } + + // Connect controller changes to knob views + synthesizerService_.setOnCcListener(new SynthesizerService.OnCcListener() { + public void onCcChange(final int channel, final int cc, final int value) { + runOnUiThread(new Runnable() { + public void run() { + if (cc == 1) { + cutoffKnob_.setValue(value * (1.0 / 127)); + } else if (cc == 2) { + resonanceKnob_.setValue(value * (1.0 / 127)); + } else if (cc == 3) { + overdriveKnob_.setValue(value * (1.0 / 127)); + } + } + }); + } + }); } public void onServiceDisconnected(ComponentName className) { synthesizerService_ = null; diff --git a/android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java b/android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java index 7971a12..2053670 100644 --- a/android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java +++ b/android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java @@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -83,10 +83,10 @@ public class KnobView extends View { diffAngle_ = 0; break; } - + case MotionEvent.ACTION_MOVE: { getDrawingRect(rect_); - + // Compare the previous angle of the finger position (relative to the center of the control) // to the new angle, and update the value accordingly. currentTouchAngle_ = knobValue_ * 2 * Math.PI * 0.8 + (Math.PI / 5.0); @@ -102,7 +102,7 @@ public class KnobView extends View { knobValue_ = currentTouchAngle_ / (2.0 * Math.PI); if (knobValue_ < 0.1) knobValue_ = 0.1; if (knobValue_ > 0.9) knobValue_ = 0.9; - knobValue_ -= 0.1; + knobValue_ -= 0.1; knobValue_ /= 0.8; previousX_ = currentX_; previousY_ = currentY_; @@ -115,7 +115,7 @@ public class KnobView extends View { break; } - + case MotionEvent.ACTION_UP: { break; } @@ -147,7 +147,9 @@ public class KnobView extends View { } /** - * Sets the current value of the knob. + * Sets the current value of the knob. Note that this call does not + * invoke the listener. The assumption is that any caller will also + * update the client. */ public void setValue(double value) { if (value < min_) { @@ -157,9 +159,6 @@ public class KnobView extends View { } else { knobValue_ = (value - min_) / (max_ - min_); } - if (listener_ != null) { - listener_.onKnobChanged(value); - } invalidate(); } @@ -189,30 +188,30 @@ public class KnobView extends View { rectF_.left = center - rectF_.height() / 2; rectF_.right = center + rectF_.height() / 2; } - + // Draw outer white glow. int[] radialGradientColors = {Color.WHITE, Color.WHITE, 0x00000000}; float[] radialGradientPositions = {0.0f, 0.87f, 1.0f}; radialGradient_ = new RadialGradient(rect_.exactCenterX(), - rect_.exactCenterY(), - Math.min(rect_.width(), rect_.height()) / 2, + rect_.exactCenterY(), + Math.min(rect_.width(), rect_.height()) / 2, radialGradientColors, - radialGradientPositions, + radialGradientPositions, TileMode.CLAMP); - + knobPaint_.setShader(radialGradient_); canvas.drawCircle(rect_.exactCenterX(), - rect_.exactCenterY(), - Math.min(rect_.width(), rect_.height()) / 2, + rect_.exactCenterY(), + Math.min(rect_.width(), rect_.height()) / 2, knobPaint_); - + // Draw outer gauge. - final int fullDark = Color.BLACK; + final int fullDark = Color.BLACK; final int guageStartColor = 0xff202050; final int guageEndColor = 0xff4040A0; - + final int adjustedStartColor = Color.argb( - 0xFF, + 0xFF, (int)(0.1875 * Color.red(guageStartColor) + (1.0 - 0.1875) * Color.red(guageEndColor)), (int)(0.1875 * Color.green(guageStartColor) + (1.0 - 0.1875) * Color.green(guageEndColor)), (int)(0.1875 * Color.blue(guageStartColor) + (1.0 - 0.1875) * Color.blue(guageEndColor))); @@ -243,20 +242,20 @@ public class KnobView extends View { rect_.exactCenterY(), Math.min(rect_.width(), rect_.height()) / 4, knobPaint_); - + // Draw inner white glow. int[] innerRadialGradientColors = { 0x00000000, 0x00000000, Color.WHITE }; float[] innerRadialGradientPositions = { 0.0f, 0.6f, 1.0f }; innerRadialGradient_ = new RadialGradient(rect_.exactCenterX(), - rect_.exactCenterY(), - Math.min(rect_.width(), rect_.height()) / 4f, + rect_.exactCenterY(), + Math.min(rect_.width(), rect_.height()) / 4f, innerRadialGradientColors, - innerRadialGradientPositions, + innerRadialGradientPositions, TileMode.CLAMP); knobPaint_.setShader(innerRadialGradient_); canvas.drawCircle(rect_.exactCenterX(), - rect_.exactCenterY(), - Math.min(rect_.width(),rect_.height()) / 4, + rect_.exactCenterY(), + Math.min(rect_.width(),rect_.height()) / 4, knobPaint_); // Draw indicator. @@ -270,7 +269,7 @@ public class KnobView extends View { (float)arcWidth, false, knobPaint_); - + // Draw text. String knobValueString = String.format("%.2f", getValue()); Typeface typeface = Typeface.create(knobPaint_.getTypeface(), Typeface.BOLD); @@ -329,7 +328,7 @@ public class KnobView extends View { if (height > width && heightMode != MeasureSpec.EXACTLY) { height = width; } - + setMeasuredDimension(width, height); }