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_;
+}