diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 96edba4..b8757cf 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -4,7 +4,10 @@
package="com.google.synthesizer"
android:versionCode="2"
android:versionName="0.9">
-
+
+
+
+
+
+
+
+
+
+
diff --git a/android/src/com/google/synthesizer/android/AndroidGlue.java b/android/src/com/google/synthesizer/android/AndroidGlue.java
index e79c87b..9a86a64 100644
--- a/android/src/com/google/synthesizer/android/AndroidGlue.java
+++ b/android/src/com/google/synthesizer/android/AndroidGlue.java
@@ -16,6 +16,11 @@ public class AndroidGlue extends MessageOutputProcessor {
*/
public native void start();
+ /**
+ * Shut down the OpenSL ES engine and audio synthesizer.
+ */
+ public native void shutdown();
+
/**
* Start or pause the actual sound generation.
*
diff --git a/android/src/com/google/synthesizer/android/ui/PianoActivity2.java b/android/src/com/google/synthesizer/android/ui/PianoActivity2.java
index 84ac602..be1b053 100644
--- a/android/src/com/google/synthesizer/android/ui/PianoActivity2.java
+++ b/android/src/com/google/synthesizer/android/ui/PianoActivity2.java
@@ -19,15 +19,27 @@ package com.google.synthesizer.android.ui;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import android.app.Activity;
+import android.content.Context;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.ToggleButton;
import com.google.synthesizer.R;
import com.google.synthesizer.android.AndroidGlue;
@@ -43,12 +55,13 @@ public class PianoActivity2 extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.piano);
piano_ = (PianoView)findViewById(R.id.piano);
volumeKnob_ = (KnobView)findViewById(R.id.volumeKnob);
presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner);
- // TODO: wire these up (preset spinner should send patch selection)
androidGlue_ = new AndroidGlue();
androidGlue_.start();
@@ -77,9 +90,17 @@ public class PianoActivity2 extends Activity {
}
});
piano_.bindTo(androidGlue_);
-
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ tryConnectUsb();
+ }
}
+ @Override
+ protected void onDestroy() {
+ androidGlue_.shutdown();
+ super.onDestroy();
+ }
+
@Override
protected void onPause() {
androidGlue_.setPlayState(false);
@@ -92,6 +113,64 @@ public class PianoActivity2 extends Activity {
super.onResume();
}
+ private UsbEndpoint getInputEndpoint(UsbInterface usbIf) {
+ int nEndpoints = usbIf.getEndpointCount();
+ for (int i = 0; i < nEndpoints; i++) {
+ UsbEndpoint endpoint = usbIf.getEndpoint(i);
+ if (endpoint.getDirection() == UsbConstants.USB_DIR_IN &&
+ endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
+ return endpoint;
+ }
+ }
+ return null;
+ }
+
+ private void startUsbThread(final UsbDeviceConnection connection, final UsbEndpoint endpoint) {
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ byte[] buf = new byte[endpoint.getMaxPacketSize()];
+ while (true) {
+ int nBytes = connection.bulkTransfer(endpoint, buf, buf.length, 10000);
+ for (int i = 0; i < nBytes; i += 4) {
+ int codeIndexNumber = buf[i] & 0xf;
+ int payloadBytes = 0;
+ if (codeIndexNumber == 8 || codeIndexNumber == 9) {
+ // TODO: pitchbend, control, etc
+ payloadBytes = 3;
+ } else if (codeIndexNumber == 12) {
+ payloadBytes = 2;
+ }
+ if (payloadBytes > 0) {
+ byte[] newBuf = new byte[payloadBytes];
+ System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes);
+ androidGlue_.onMessage(newBuf);
+ }
+ }
+ }
+ }
+ });
+ thread.start();
+ }
+ private void tryConnectUsb() {
+ UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+ HashMap deviceList = usbManager.getDeviceList();
+ TextView label = (TextView)findViewById(R.id.volumeLabel);
+ if (!deviceList.isEmpty()) {
+ UsbDevice device = deviceList.values().iterator().next();
+ //label.setText("ic:" + device.getInterfaceCount());
+ UsbInterface usbIf = device.getInterface(1);
+ UsbDeviceConnection connection = usbManager.openDevice(device);
+ if (connection != null) {
+ connection.claimInterface(usbIf, true);
+ UsbEndpoint endpoint = getInputEndpoint(usbIf);
+ //label.setText(endpoint.toString());
+ startUsbThread(connection, endpoint);
+ } else {
+ label.setText("error opening device");
+ }
+ }
+ }
+
private AndroidGlue androidGlue_;
private PianoView piano_;
private KnobView volumeKnob_;
diff --git a/android/src/com/google/synthesizer/android/widgets/ChordGridView.java b/android/src/com/google/synthesizer/android/widgets/ChordGridView.java
index 92db1fc..4bab024 100644
--- a/android/src/com/google/synthesizer/android/widgets/ChordGridView.java
+++ b/android/src/com/google/synthesizer/android/widgets/ChordGridView.java
@@ -73,7 +73,7 @@ public class ChordGridView extends View {
*/
private void notifyNoteDown(double logFrequency, int finger, boolean retriggerIfOn) {
if (pianoViewListener_ != null) {
- pianoViewListener_.noteDown(logFrequency, finger, retriggerIfOn);
+ pianoViewListener_.noteDown(logFrequency, finger, retriggerIfOn, 1.0f);
}
}
@@ -282,7 +282,8 @@ public class ChordGridView extends View {
*/
public void bindTo(final MultiChannelSynthesizer synth, final int channel) {
this.setPianoViewListener(new PianoViewListener() {
- public void noteDown(double logFrequency, int finger, boolean retriggerIfOn) {
+ public void noteDown(double logFrequency, int finger, boolean retriggerIfOn,
+ float pressure) {
synth.getChannel(channel).setPitch(logFrequency, finger);
synth.getChannel(channel).turnOn(retriggerIfOn, finger);
}
diff --git a/android/src/com/google/synthesizer/android/widgets/piano/PianoView.java b/android/src/com/google/synthesizer/android/widgets/piano/PianoView.java
index c6fa73d..d68003f 100644
--- a/android/src/com/google/synthesizer/android/widgets/piano/PianoView.java
+++ b/android/src/com/google/synthesizer/android/widgets/piano/PianoView.java
@@ -113,9 +113,10 @@ public class PianoView extends View {
* @param logFrequency - the log frequency of the new note.
* @param retriggerIfOn - true if this is a new touch, rather than just moving.
*/
- private void notifyNoteDown(double logFrequency, int finger, boolean retriggerIfOn) {
+ private void notifyNoteDown(double logFrequency, int finger, boolean retriggerIfOn,
+ float pressure) {
if (pianoViewListener_ != null) {
- pianoViewListener_.noteDown(logFrequency, finger, retriggerIfOn);
+ pianoViewListener_.noteDown(logFrequency, finger, retriggerIfOn, pressure);
}
}
@@ -147,7 +148,7 @@ public class PianoView extends View {
* Called to handle touch down events.
* Returns true iff we need to redraw.
*/
- protected boolean onTouchDown(int finger, int x, int y) {
+ protected boolean onTouchDown(int finger, int x, int y, float pressure) {
// Look through keys from top to bottom, and set the first one found as down, the rest as up.
PianoKey keyDown = null;
boolean redraw = false;
@@ -165,7 +166,7 @@ public class PianoView extends View {
}
}
if (keyDown instanceof NotePianoKey) {
- notifyNoteDown(((NotePianoKey)keyDown).getLogFrequency(), finger, true);
+ notifyNoteDown(((NotePianoKey)keyDown).getLogFrequency(), finger, true, pressure);
}
return redraw;
}
@@ -173,7 +174,7 @@ public class PianoView extends View {
/**
* Called to handle touch move events.
*/
- protected boolean onTouchMove(int finger, int x, int y) {
+ protected boolean onTouchMove(int finger, int x, int y, float pressure) {
// Look through keys from top to bottom, and set the first one found as moved, the rest as up.
PianoKey keyDown = null;
boolean redraw = false;
@@ -193,8 +194,11 @@ public class PianoView extends View {
}
}
if (keyDown instanceof NotePianoKey) {
+ if (!usePressure_) {
+ pressure = 0.5f;
+ }
if (!wasPressed) {
- notifyNoteDown(((NotePianoKey)keyDown).getLogFrequency(), finger, false);
+ notifyNoteDown(((NotePianoKey)keyDown).getLogFrequency(), finger, false, pressure);
}
} else {
notifyNoteUp(finger);
@@ -229,17 +233,18 @@ public class PianoView extends View {
if (pointerId < FINGERS) {
int x = (int)event.getX();
int y = (int)event.getY();
- redraw |= onTouchDown(pointerId, x, y);
+ float pressure = event.getPressure();
+ redraw |= onTouchDown(pointerId, x, y, pressure);
}
} else if (actionCode == MotionEvent.ACTION_POINTER_DOWN) {
- int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
- if (pointerId < FINGERS) {
- int pointerIndex = event.findPointerIndex(pointerId);
- if (pointerIndex >= 0) {
- int x = (int)event.getX(pointerIndex);
- int y = (int)event.getY(pointerIndex);
- redraw |= onTouchDown(pointerId, x, y);
- }
+ int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ int pointerId = event.getPointerId(pointerIndex);
+ if (pointerId < FINGERS && pointerId >= 0) {
+ int x = (int)event.getX(pointerIndex);
+ int y = (int)event.getY(pointerIndex);
+ float pressure = event.getPressure(pointerIndex);
+ redraw |= onTouchDown(pointerId, x, y, pressure);
}
} else if (actionCode == MotionEvent.ACTION_MOVE) {
for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
@@ -250,7 +255,8 @@ public class PianoView extends View {
if (pointerIndex >= 0) {
int x = (int)event.getX(pointerIndex);
int y = (int)event.getY(pointerIndex);
- redraw |= onTouchMove(pointerId, x, y);
+ float pressure = event.getPressure(pointerIndex);
+ redraw |= onTouchMove(pointerId, x, y, pressure);
}
}
} else if (actionCode == MotionEvent.ACTION_UP) {
@@ -272,14 +278,15 @@ public class PianoView extends View {
}
}
} else if (actionCode == MotionEvent.ACTION_POINTER_UP) {
- int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+ int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ int pointerId = event.getPointerId(pointerIndex);
if (pointerId < FINGERS) {
redraw |= onTouchUp(pointerId);
}
- // Clean up any other pointers that have disappeared.
+ // Clean up any other pointers that have disappeared. Note: this is probably not necessary.
for (pointerId = 0; pointerId < FINGERS; ++pointerId) {
boolean found = false;
- for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
+ for (pointerIndex = 0; pointerIndex < event.getPointerCount(); ++pointerIndex) {
if (pointerId == event.getPointerId(pointerIndex)) {
found = true;
break;
@@ -346,7 +353,8 @@ public class PianoView extends View {
*/
public void bindTo(final MultiChannelSynthesizer synth, final int channel) {
this.setPianoViewListener(new PianoViewListener() {
- public void noteDown(double logFrequency, int finger, boolean retriggerIfOn) {
+ public void noteDown(double logFrequency, int finger, boolean retriggerIfOn,
+ float pressure) {
synth.getChannel(channel).setPitch(logFrequency, finger);
synth.getChannel(channel).turnOn(retriggerIfOn, finger);
}
@@ -357,25 +365,26 @@ public class PianoView extends View {
}
/**
- * Connects the PianoView to an AndroidGlue. This should probably be a MidiListener instead,
- * though...
+ * Connects the PianoView to an MidiListener.
*/
- public void bindTo(final MidiListener midiSink) {
+ public void bindTo(final MidiListener midiListener) {
this.setPianoViewListener(new PianoViewListener() {
{
fingerMap_ = new HashMap();
}
- public void noteDown(double logFrequency, int finger, boolean retriggerIfOn) {
+ public void noteDown(double logFrequency, int finger, boolean retriggerIfOn,
+ float pressure) {
noteUp(finger);
int midiNote = Note.getKeyforLog12TET(logFrequency);
fingerMap_.put(finger, midiNote);
- midiSink.onNoteOn(0, midiNote, 64);
+ int midiPressure = Math.max(1, Math.min(127, (int)(127 * pressure)));
+ midiListener.onNoteOn(0, midiNote, midiPressure);
}
public void noteUp(int finger) {
if (fingerMap_.containsKey(finger)) {
int midiNote = fingerMap_.get(finger);
fingerMap_.remove(finger);
- midiSink.onNoteOff(0, midiNote, 64);
+ midiListener.onNoteOff(0, midiNote, 64);
}
}
private Map fingerMap_;
@@ -402,4 +411,7 @@ public class PianoView extends View {
// The number of simultaneous fingers supported by this control.
protected static final int FINGERS = 5;
+
+ // Whether to use pressure (doesn't work well on all hardware)
+ private boolean usePressure_ = true;
}
diff --git a/android/src/com/google/synthesizer/android/widgets/piano/PianoViewListener.java b/android/src/com/google/synthesizer/android/widgets/piano/PianoViewListener.java
index cf437a5..5c7a087 100644
--- a/android/src/com/google/synthesizer/android/widgets/piano/PianoViewListener.java
+++ b/android/src/com/google/synthesizer/android/widgets/piano/PianoViewListener.java
@@ -23,7 +23,7 @@ public interface PianoViewListener {
* @param logFrequency - the log frequency of the note pressed.
* @param retriggerIfOn - true if this is a new touch, rather than just moving.
*/
- void noteDown(double logFrequency, int finger, boolean retriggerIfOn);
+ void noteDown(double logFrequency, int finger, boolean retriggerIfOn, float pressure);
/**
* The note was released.
diff --git a/cpp/src/android_glue.cc b/cpp/src/android_glue.cc
index 97d1a8e..8f6604b 100644
--- a/cpp/src/android_glue.cc
+++ b/cpp/src/android_glue.cc
@@ -1,3 +1,19 @@
+/*
+* Copyright 2012 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.
+*/
+
#include
#include
#include
@@ -16,7 +32,7 @@ RingBuffer *ring_buffer;
SynthUnit *synth_unit;
const int N_BUFFERS = 2;
-const int BUFFER_SIZE = 64;
+const int BUFFER_SIZE = 384;
int16_t buffer[BUFFER_SIZE * N_BUFFERS];
int cur_buffer = 0;
@@ -32,23 +48,22 @@ static SLObjectItf outputMixObject = NULL;
// buffer queue player interfaces
static SLObjectItf bqPlayerObject = NULL;
static SLPlayItf bq_player_play;
-static SLVolumeItf bq_player_volume;
static SLAndroidSimpleBufferQueueItf bq_player_buffer_queue;
static SLBufferQueueItf buffer_queue_itf;
extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf,
- void *data) {
+ void *data) {
if (count >= 1000) return;
int16_t *buf_ptr = buffer + BUFFER_SIZE * cur_buffer;
synth_unit->GetSamples(BUFFER_SIZE, buf_ptr);
SLresult result = (*queueItf)->Enqueue(bq_player_buffer_queue,
- buf_ptr, BUFFER_SIZE * 2);
+ buf_ptr, BUFFER_SIZE * 2);
assert(SL_RESULT_SUCCESS == result);
cur_buffer = (cur_buffer + 1) % N_BUFFERS;
}
void CreateEngine() {
- SLresult result;
+SLresult result;
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result);
@@ -64,7 +79,8 @@ void CreateEngine() {
assert(SL_RESULT_SUCCESS == result);
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
-}
+ LOGI("engine started");
+ }
extern "C" JNIEXPORT void JNICALL
Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
@@ -73,7 +89,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, N_BUFFERS};
SLDataFormat_PCM format_pcm = {
- SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_48,
+ SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN
// TODO: compute real endianness
@@ -97,14 +113,11 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
&bq_player_buffer_queue);
assert(SL_RESULT_SUCCESS == result);
- result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME,
- &bq_player_volume);
- assert(SL_RESULT_SUCCESS == result);
result = (*bq_player_buffer_queue)->RegisterCallback(bq_player_buffer_queue,
- &BqPlayerCallback, NULL);
+ &BqPlayerCallback, NULL);
assert(SL_RESULT_SUCCESS == result);
- double sample_rate = 48000.0;
+ double sample_rate = 44100.0;
Freqlut::init(sample_rate);
Sin::init();
ring_buffer = new RingBuffer();
@@ -118,6 +131,31 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
assert(SL_RESULT_SUCCESS == result);
}
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_synthesizer_android_AndroidGlue_shutdown(JNIEnv *env,
+ jobject thiz) {
+ LOGI("shutting down engine");
+ if (bqPlayerObject != NULL) {
+ (*bqPlayerObject)->Destroy(bqPlayerObject);
+ bqPlayerObject = NULL;
+ bq_player_play = NULL;
+ bq_player_buffer_queue = NULL;
+ }
+ if (outputMixObject != NULL) {
+ (*outputMixObject)->Destroy(outputMixObject);
+ outputMixObject = NULL;
+ }
+ if (engineObject != NULL) {
+ (*engineObject)->Destroy(engineObject);
+ engineObject = NULL;
+ engineEngine = NULL;
+ }
+ delete ring_buffer;
+ ring_buffer = NULL;
+ delete synth_unit;
+ synth_unit = NULL;
+}
+
extern "C" JNIEXPORT void JNICALL
Java_com_google_synthesizer_android_AndroidGlue_sendMidi(JNIEnv *env,
jobject thiz, jbyteArray jb) {