diff --git a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java index 88f5f50..cca19eb 100755 --- a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java +++ b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java @@ -86,15 +86,6 @@ 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]; @@ -109,6 +100,31 @@ public class SynthesizerService extends Service { 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); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { 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 { 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) { onCcListener_ = onCcListener; } + public void setOnNoteListeners(OnNoteListener onNoteOn, OnNoteListener onNoteOff) { + onNoteOnListener_ = onNoteOn; + onNoteOffListener_ = onNoteOff; + } + private MidiListener midiListener_; // Binder to use for Activities in this process. @@ -298,4 +324,6 @@ public class SynthesizerService extends Service { // Plumbing for MIDI events private OnCcListener onCcListener_; + private OnNoteListener onNoteOnListener_; + private OnNoteListener onNoteOffListener_; } diff --git a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java index dbf5c95..e406c0e 100644 --- a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java @@ -72,8 +72,9 @@ public class PianoActivity2 extends Activity { presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View view, int position, long id) { - synthesizerService_.getMidiListener().onProgramChange(0, position); - sendMidiBytes(new byte[] {(byte)0xc0, (byte)position}); + if (synthesizerService_ != null) { + synthesizerService_.getMidiListener().onProgramChange(0, position); + } } public void onNothingSelected(AdapterView parent) { } @@ -81,20 +82,27 @@ public class PianoActivity2 extends Activity { cutoffKnob_.setKnobListener(new KnobListener() { public void onKnobChanged(double newValue) { - int value = (int)Math.round(newValue * 127); - synthesizerService_.getMidiListener().onController(0, 1, value); + // Maybe actually bind these at synthesizer service connection time? + if (synthesizerService_ != null) { + int value = (int)Math.round(newValue * 127); + synthesizerService_.getMidiListener().onController(0, 1, value); + } } }); resonanceKnob_.setKnobListener(new KnobListener() { public void onKnobChanged(double newValue) { - int value = (int)Math.round(newValue * 127); - synthesizerService_.getMidiListener().onController(0, 2, value); + if (synthesizerService_ != null) { + int value = (int)Math.round(newValue * 127); + synthesizerService_.getMidiListener().onController(0, 2, value); + } } }); overdriveKnob_.setKnobListener(new KnobListener() { public void onKnobChanged(double newValue) { - int value = (int)Math.round(newValue * 127); - synthesizerService_.getMidiListener().onController(0, 3, value); + if (synthesizerService_ != null) { + 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) { 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 2053670..0249f2f 100644 --- a/android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java +++ b/android/src/com/levien/synthesizer/android/widgets/knob/KnobView.java @@ -21,14 +21,14 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Shader.TileMode; import android.graphics.SweepGradient; 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.Log; import android.view.MotionEvent; @@ -52,11 +52,14 @@ public class KnobView extends View { knobValue_ = a.getFloat(R.styleable.KnobView_value, 0.5f); min_ = a.getFloat(R.styleable.KnobView_min, 0.0f); max_ = a.getFloat(R.styleable.KnobView_max, 1.0f); + a.recycle(); // Set up the drawing structures. knobPaint_ = new Paint(); knobPaint_.setAntiAlias(true); knobPaint_.setColor(Color.WHITE); + float density = getResources().getDisplayMetrics().density; + knobPaint_.setStrokeWidth(2.0f * density); rect_ = new Rect(); rectF_ = new RectF(); textRect_ = new Rect(); @@ -64,7 +67,8 @@ public class KnobView extends View { // The listener has to be set later. 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_.setColor(Color.WHITE); knobPaint_.setStyle(Style.STROKE); - knobPaint_.setStrokeWidth(4.0f); final float arcWidth = 15.0f; canvas.drawArc(rectF_, (float)(knobValue_ * 360 * 0.8 + 90 - arcWidth / 2 + 36), diff --git a/android/src/com/levien/synthesizer/android/widgets/piano/PianoKey.java b/android/src/com/levien/synthesizer/android/widgets/piano/PianoKey.java index d1f114f..44e987b 100644 --- a/android/src/com/levien/synthesizer/android/widgets/piano/PianoKey.java +++ b/android/src/com/levien/synthesizer/android/widgets/piano/PianoKey.java @@ -1,12 +1,12 @@ /* * Copyright 2011 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. @@ -44,6 +44,8 @@ public abstract class PianoKey { fillPaint_.setStyle(Paint.Style.FILL); strokePaint_.setStyle(Paint.Style.STROKE); strokePaint_.setColor(Color.BLACK); + float strokeWidth = 1.0f * piano.getResources().getDisplayMetrics().density; + strokePaint_.setStrokeWidth(strokeWidth); } /** @@ -148,7 +150,7 @@ public abstract class PianoKey { // It's +2 to reserve space for the octave-up/down buttons. return drawingRect.width() / ((WHITE_KEYS.length * octaves) + 2); } - + /** * Utility function to calculate the height that a standard white key on this keyboard should be. */ diff --git a/android/src/com/levien/synthesizer/android/widgets/piano/PianoView.java b/android/src/com/levien/synthesizer/android/widgets/piano/PianoView.java index 2ab1b19..d1b9d62 100644 --- a/android/src/com/levien/synthesizer/android/widgets/piano/PianoView.java +++ b/android/src/com/levien/synthesizer/android/widgets/piano/PianoView.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. @@ -47,6 +47,7 @@ public class PianoView extends View { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PianoView); octaves_ = a.getInteger(R.styleable.PianoView_octaves, 1); 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. drawingRect_ = new Rect(); @@ -222,7 +223,7 @@ public class PianoView extends View { notifyNoteUp(finger); return redraw; } - + /** * Handler for all touch events. @@ -319,7 +320,7 @@ public class PianoView extends View { if (redraw) { invalidate(); } - return true; + return true; } /** @@ -335,7 +336,7 @@ public class PianoView extends View { int width = 0; int height = 0; - + switch (widthMode) { case MeasureSpec.EXACTLY: width = widthSize; @@ -347,7 +348,7 @@ public class PianoView extends View { width = 10; break; } - + switch (heightMode) { case MeasureSpec.EXACTLY: height = heightSize; @@ -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. // // This is basically a stack variable for onDraw. It's a member variable only so that we can @@ -428,7 +452,7 @@ public class PianoView extends View { // The number of simultaneous fingers supported by this control. protected static final int FINGERS = 10; - + // Whether to use pressure (doesn't work well on all hardware) private boolean usePressure_ = false; }