From ceba5012c8fbbd6de79fa0b27ac5b2a617215099 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Dec 2013 22:53:37 -0800 Subject: [PATCH] Start new keyboard view This patch contains a new keyboard view with an adjustable layout. As of this version, it draws and displays notes from the MIDI keyboard but doesn't handle any of its own touch events. --- android/res/values-v11/styles.xml | 5 + android/res/values/styles.xml | 5 + .../android/widgets/keyboard/KeySpec.java | 28 ++++ .../widgets/keyboard/KeyboardSpec.java | 89 +++++++++++ .../widgets/keyboard/KeyboardView.java | 145 ++++++++++++++++++ 5 files changed, 272 insertions(+) create mode 100644 android/res/values-v11/styles.xml create mode 100644 android/res/values/styles.xml create mode 100644 android/src/com/levien/synthesizer/android/widgets/keyboard/KeySpec.java create mode 100644 android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardSpec.java create mode 100644 android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java diff --git a/android/res/values-v11/styles.xml b/android/res/values-v11/styles.xml new file mode 100644 index 0000000..f1ec71d --- /dev/null +++ b/android/res/values-v11/styles.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/res/values/styles.xml b/android/res/values/styles.xml new file mode 100644 index 0000000..578c792 --- /dev/null +++ b/android/res/values/styles.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/src/com/levien/synthesizer/android/widgets/keyboard/KeySpec.java b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeySpec.java new file mode 100644 index 0000000..bfea32a --- /dev/null +++ b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeySpec.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.levien.synthesizer.android.widgets.keyboard; + +import android.graphics.RectF; + +/** + * A single key in a keyboard spec. + */ +public class KeySpec { + public RectF rect; + // we may add an optional draw poly later + public int color; +} diff --git a/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardSpec.java b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardSpec.java new file mode 100644 index 0000000..31747e2 --- /dev/null +++ b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardSpec.java @@ -0,0 +1,89 @@ +/* + * Copyright 2013 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.levien.synthesizer.android.widgets.keyboard; + +import android.graphics.Color; +import android.graphics.RectF; + +public class KeyboardSpec { + public KeyboardSpec(int nkeys, float repeatWidth, float height) { + keys = new KeySpec[nkeys]; + this.repeatWidth = repeatWidth; + this.height = height; + } + + public void addKey(KeySpec key) { + keys[ix_++] = key; + this.maxX = Math.max(this.maxX, key.rect.right); + } + + public void addKey(float x0, float y0, float w, float h, int color) { + KeySpec ks = new KeySpec(); + ks.rect = new RectF(x0, y0, x0 + w, y0 + h); + ks.color = color; + addKey(ks); + } + + public static KeyboardSpec make2Layer() { + KeyboardSpec ks = new KeyboardSpec(12, 84, 24); + final int w = Color.WHITE; + final int b = Color.BLACK; + ks.addKey(0, 12, 12, 12, w); // C + ks.addKey(6, 0, 12, 12, b); // C# + ks.addKey(12, 12, 12, 12, w); // D + ks.addKey(18, 0, 12, 12, b); // D# + ks.addKey(24, 12, 12, 12, w); // E + ks.addKey(36, 12, 12, 12, w); // F + ks.addKey(42, 0, 12, 12, b); // F# + ks.addKey(48, 12, 12, 12, w); // G + ks.addKey(54, 0, 12, 12, b); // G# + ks.addKey(60, 12, 12, 12, w); // A + ks.addKey(66, 0, 12, 12, b); // A# + ks.addKey(72, 12, 12, 12, w); // B + return ks; + } + + public static KeyboardSpec make3Layer() { + KeyboardSpec ks = new KeyboardSpec(24, 84, 32); + final int w = Color.WHITE; + final int b = Color.BLACK; + for (int oct = 0; oct < 2; oct++) { + float x0 = 42 * oct; + float y1 = 20 - 12 * oct; + float y2 = 28 - y1; + ks.addKey(x0 + 0, y1, 12, 12, w); // C + ks.addKey(x0 + 4, 0, 8, 8, b); // C# + ks.addKey(x0 + 6, y2, 12, 12, w); // D + ks.addKey(x0 + 12, 0, 8, 8, b); // D# + ks.addKey(x0 + 12, y1, 12, 12, w); // E + ks.addKey(x0 + 18, y2, 12, 12, w); // F + ks.addKey(x0 + 21, 0, 8, 8, b); // F# + ks.addKey(x0 + 24, y1, 12, 12, w); // G + ks.addKey(x0 + 29, 0, 8, 8, b); // G# + ks.addKey(x0 + 30, y2, 12, 12, w); // A + ks.addKey(x0 + 37, 0, 8, 8, b); // A# + ks.addKey(x0 + 36, y1, 12, 12, w); // B + } + return ks; + } + + public KeySpec keys[]; + public float repeatWidth; + public float height; + public float maxX; + private int ix_; +} diff --git a/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java new file mode 100644 index 0000000..b1c5421 --- /dev/null +++ b/android/src/com/levien/synthesizer/android/widgets/keyboard/KeyboardView.java @@ -0,0 +1,145 @@ +/* + * Copyright 2013 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.levien.synthesizer.android.widgets.keyboard; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +public class KeyboardView extends View { + public KeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + nKeys_ = 24; // TODO: make configurable + firstKey_ = 48; + noteStatus_ = new byte[128]; + drawingRect_ = new Rect(); + paint_ = new Paint(); + paint_.setAntiAlias(true); + float density = getResources().getDisplayMetrics().density; + strokeWidth_ = 1.0f * density; + paint_.setStrokeWidth(strokeWidth_); + } + + public void setKeyboardSpec(KeyboardSpec keyboardSpec) { + keyboardSpec_ = keyboardSpec; + keyboardScale_ = 1.0f / keyboardSpec_.repeatWidth * keyboardSpec_.keys.length / nKeys_; + invalidate(); + } + + public void onNote(int note, int velocity) { + if (note >= 0 && note < 128) { + noteStatus_[note] = (byte)velocity; + invalidate(); // could do smarter invalidation, whatev + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + getDrawingRect(drawingRect_); + float xscale = (drawingRect_.width() - strokeWidth_) * keyboardScale_; + float yscale = (drawingRect_.height() - strokeWidth_) / keyboardSpec_.height; + float x0 = drawingRect_.left + strokeWidth_ * 0.5f; + 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; + float width = ks.rect.width() * xscale - strokeWidth_; + float height = ks.rect.height() * yscale - strokeWidth_; + int vel = noteStatus_[i + firstKey_]; + if (vel == 0) { + paint_.setColor(ks.color); + } else { + // green->yellow->red gradient, dependent on velocity + int color; + if (vel < 64) { + color = 0xff00ff00 + (vel << 18); + } else { + color = 0xffffff00 - ((vel - 64) << 10); + } + paint_.setColor(color); + } + paint_.setStyle(Style.FILL); + canvas.drawRect(x, y, x + width, y + height, paint_); + if (ks.color != Color.BLACK) { + paint_.setColor(Color.BLACK); + paint_.setStyle(Style.STROKE); + canvas.drawRect(x, y, x + width + strokeWidth_, y + height + strokeWidth_, paint_); + } + } + } + + /** + * Layout measurement for this widget. + * This method just sets a basic minimum size and makes the widget maximized otherwise. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + int width = 0; + int height = 0; + + switch (widthMode) { + case MeasureSpec.EXACTLY: + width = widthSize; + break; + case MeasureSpec.AT_MOST: + width = widthSize; + break; + case MeasureSpec.UNSPECIFIED: + width = 10; + break; + } + + switch (heightMode) { + case MeasureSpec.EXACTLY: + height = heightSize; + break; + case MeasureSpec.AT_MOST: + height = heightSize; + break; + case MeasureSpec.UNSPECIFIED: + height = 10; + break; + } + + setMeasuredDimension(width, height); + } + + private Rect drawingRect_; + private Paint paint_; + private float strokeWidth_; + private float keyboardScale_; + + private KeyboardSpec keyboardSpec_; + private int nKeys_; + private int firstKey_; + private byte[] noteStatus_; +}