From c3509e50c192310c5084f3abcb4d379d3b36d143 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 27 Mar 2012 21:47:59 -0700 Subject: [PATCH] Adding the glue logic (and an activity for testing) to run the C++ sound engine on Android. Note that this particular patchset disables the Java-based synthesis, so probably shouldn't be committed as-is to mainline. --- .../.externalToolBuilders/NDK Builder.launch | 10 ++ android/jni/Android.mk | 18 +++ android/jni/Application.mk | 1 + android/project.properties | 14 ++ .../synthesizer/android/AndroidGlue.java | 14 ++ .../android/ui/PianoActivity2.java | 66 ++++++++ cpp/src/android_glue.cc | 145 ++++++++++++++++++ cpp/src/synth.h | 11 +- cpp/src/synth_unit.cc | 1 + 9 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 android/.externalToolBuilders/NDK Builder.launch create mode 100644 android/jni/Android.mk create mode 100644 android/jni/Application.mk create mode 100644 android/project.properties create mode 100644 android/src/com/google/synthesizer/android/AndroidGlue.java create mode 100644 android/src/com/google/synthesizer/android/ui/PianoActivity2.java create mode 100644 cpp/src/android_glue.cc diff --git a/android/.externalToolBuilders/NDK Builder.launch b/android/.externalToolBuilders/NDK Builder.launch new file mode 100644 index 0000000..0f8f837 --- /dev/null +++ b/android/.externalToolBuilders/NDK Builder.launch @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk new file mode 100644 index 0000000..249e1df --- /dev/null +++ b/android/jni/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH := $(call my-dir)/../../cpp/src + +include $(CLEAR_VARS) +LOCAL_MODULE := synth +LOCAL_CPP_EXTENSION := .cc +LOCAL_SRC_FILES := dx7note.cc \ + env.cc \ + fm_core.cc \ + fm_op_kernel.cc \ + freqlut.cc \ + resofilter.cc \ + ringbuffer.cc \ + sawtooth.cc \ + sin.cc \ + synth_unit.cc + +include $(BUILD_SHARED_LIBRARY) + diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100644 index 0000000..caf3b26 --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1 @@ +APP_STL := stlport_static diff --git a/android/project.properties b/android/project.properties new file mode 100644 index 0000000..0840b4a --- /dev/null +++ b/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-15 diff --git a/android/src/com/google/synthesizer/android/AndroidGlue.java b/android/src/com/google/synthesizer/android/AndroidGlue.java new file mode 100644 index 0000000..165d539 --- /dev/null +++ b/android/src/com/google/synthesizer/android/AndroidGlue.java @@ -0,0 +1,14 @@ +package com.google.synthesizer.android; + + +public class AndroidGlue { + + public native void start(); + public native void setPlayState(boolean isPlaying); + public native void sendMidi(byte[] midiData); + + static { + System.loadLibrary("synth"); + } +} + diff --git a/android/src/com/google/synthesizer/android/ui/PianoActivity2.java b/android/src/com/google/synthesizer/android/ui/PianoActivity2.java new file mode 100644 index 0000000..0ec8fd0 --- /dev/null +++ b/android/src/com/google/synthesizer/android/ui/PianoActivity2.java @@ -0,0 +1,66 @@ +/* + * 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. + */ + +package com.google.synthesizer.android.ui; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.Spinner; + +import com.google.synthesizer.R; +import com.google.synthesizer.android.AndroidGlue; +import com.google.synthesizer.android.widgets.knob.KnobView; +import com.google.synthesizer.android.widgets.piano.PianoView; + +/** + * Activity for simply playing the piano. + * This version is hacked up to send MIDI to the C++ engine. This needs to + * be refactored to make it cleaner. + */ +public class PianoActivity2 extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + 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(); + + piano_.bindTo(androidGlue_); + } + + @Override + protected void onPause() { + androidGlue_.setPlayState(false); + super.onPause(); + } + + @Override + protected void onResume() { + androidGlue_.setPlayState(true); + super.onResume(); + } + + private AndroidGlue androidGlue_; + private PianoView piano_; + private KnobView volumeKnob_; + private Spinner presetSpinner_; +} diff --git a/cpp/src/android_glue.cc b/cpp/src/android_glue.cc new file mode 100644 index 0000000..a1e4505 --- /dev/null +++ b/cpp/src/android_glue.cc @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include + +#include +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__) + +#include "synth.h" +#include "freqlut.h" +#include "sin.h" +#include "synth_unit.h" + +RingBuffer *ring_buffer; +SynthUnit *synth_unit; + +const int N_BUFFERS = 2; +const int BUFFER_SIZE = 64; + +int16_t buffer[BUFFER_SIZE * N_BUFFERS]; +int cur_buffer = 0; +int count = 0; + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine; + +// output mix interfaces +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) { + 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); + assert(SL_RESULT_SUCCESS == result); + cur_buffer = (cur_buffer + 1) % N_BUFFERS; +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_synthesizer_android_AndroidGlue_hello(JNIEnv *env, + jobject thiz) { + LOGI("here %d!", 42); + return 42; +} + +void CreateEngine() { + SLresult result; + result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, + &engineEngine); + assert(SL_RESULT_SUCCESS == result); + + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, + 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env, + jobject thiz) { + CreateEngine(); + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = + {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, N_BUFFERS}; + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_48, + SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN + // TODO: compute real endianness + }; + SLDataSource audio_src = {&loc_bufq, &format_pcm}; + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, + outputMixObject}; + SLDataSink audio_sink = {&loc_outmix, NULL}; + const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + SLresult result; + result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, + &audio_src, &audio_sink, 2, ids, req); + assert(SL_RESULT_SUCCESS == result); + result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, + &bq_player_play); + assert(SL_RESULT_SUCCESS == result); + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, + &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); + assert(SL_RESULT_SUCCESS == result); + + double sample_rate = 48000.0; + Freqlut::init(sample_rate); + Sin::init(); + ring_buffer = new RingBuffer(); + synth_unit = new SynthUnit(ring_buffer); + for (int i = 0; i < N_BUFFERS - 1; ++i) { + BqPlayerCallback(bq_player_buffer_queue, NULL); + } + + result = (*bq_player_play)->SetPlayState(bq_player_play, + SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_synthesizer_android_AndroidGlue_sendMidi(JNIEnv *env, + jobject thiz, jbyteArray jb) { + uint8_t *data = (uint8_t *)env->GetByteArrayElements(jb, NULL); + if (data != NULL) { + ring_buffer->Write(data, env->GetArrayLength(jb)); + env->ReleaseByteArrayElements(jb, (jbyte *)data, JNI_ABORT); + } +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_synthesizer_android_AndroidGlue_setPlayState(JNIEnv *env, + jobject thiz, jboolean isPlaying) { + SLresult result = (*bq_player_play)->SetPlayState(bq_player_play, + isPlaying ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_PAUSED); + assert(SL_RESULT_SUCCESS == result); +} + diff --git a/cpp/src/synth.h b/cpp/src/synth.h index f5aca84..8074ac6 100644 --- a/cpp/src/synth.h +++ b/cpp/src/synth.h @@ -29,14 +29,15 @@ #define SynthMemoryBarrier() OSMemoryBarrier() #elif defined(__GNUC__) #if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) -#define SynthMemoryBarrier __sync_synchronize() -#else -#warning Memory barrier is not enabled +#define SynthMemoryBarrier() __sync_synchronize() #endif -#warning Memory barrier is not enabled #endif // #undef SynthMemoryBarrier() -// #define SynthMemoryBarrier() + +#ifndef SynthMemoryBarrier +#warning Memory barrier is not enabled +#define SynthMemoryBarrier() +#endif #endif // __SYNTH_H diff --git a/cpp/src/synth_unit.cc b/cpp/src/synth_unit.cc index 406dd12..448c66a 100644 --- a/cpp/src/synth_unit.cc +++ b/cpp/src/synth_unit.cc @@ -39,6 +39,7 @@ SynthUnit::SynthUnit(RingBuffer *ring_buffer) { input_buffer_index_ = 0; std::memcpy(patch_data_, epiano, sizeof(epiano)); current_patch_ = 0; + current_note_ = 0; } // Transfer as many bytes as possible from ring buffer to input buffer.