/* * 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 #include #include #include #include #include #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__) #include "synth.h" #include "synth_unit.h" RingBuffer *ring_buffer; RingBuffer *stats_ring_buffer; SynthUnit *synth_unit; const int N_BUFFERS = 2; const int MAX_BUFFER_SIZE = 1024; int buffer_size; int16_t buffer[MAX_BUFFER_SIZE * N_BUFFERS]; int cur_buffer = 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 SLAndroidSimpleBufferQueueItf bq_player_buffer_queue; static SLBufferQueueItf buffer_queue_itf; static double ts_to_double(const struct timespec *tp) { return tp->tv_sec + 1e-9 * tp->tv_nsec; } // Roll our own decimal printing to guarantee no allocs static size_t simple_ftoa(char *buf, double f) { size_t i = 0; uint32_t intpart = (uint32_t)f; uint32_t fracpart = (int)((f - intpart) * 1000000); do { buf[i++] = '0' + intpart % 10; intpart /= 10; } while (intpart > 0); for (size_t j = 0; j < i >> 1; j++) { char tmp = buf[j]; buf[j] = buf[i - j - 1]; buf[i - j - 1] = tmp; } buf[i++] = '.'; const size_t nfrac = 6; for (size_t j = 0; j < nfrac; j++) { buf[i + nfrac - 1 - j] = '0' + fracpart % 10; fracpart /= 10; } return i + nfrac; } extern "C" void BqPlayerCallback(SLAndroidSimpleBufferQueueItf queueItf, void *data) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); double start_time = ts_to_double(&tp); int16_t *buf_ptr = buffer + buffer_size * cur_buffer; synth_unit->GetSamples(buffer_size, buf_ptr); char buf[64]; //uint8_t *mem = new uint8_t[1024]; //delete[] mem; //sprintf(buf, "%.6f", start_time); clock_gettime(CLOCK_MONOTONIC, &tp); double end_time = ts_to_double(&tp); 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; size_t i = 0; buf[i++] = 't'; buf[i++] = 's'; buf[i++] = ' '; i += simple_ftoa(buf + i, start_time); buf[i++] = ' '; i += simple_ftoa(buf + i, end_time); buf[i++] = '\n'; buf[i] = 0; if (i <= stats_ring_buffer->WriteBytesAvailable()) { stats_ring_buffer->Write((const uint8_t *)buf, i); } // Could potentially defer writing indication of overrun, but probably // not worth it. } 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); LOGI("engine started"); } extern "C" JNIEXPORT void JNICALL Java_com_levien_synthesizer_android_AndroidGlue_start(JNIEnv *env, jobject thiz, jint sample_rate, jint buf_size) { CreateEngine(); SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, N_BUFFERS}; SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, sample_rate * 1000, 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 = (*bq_player_buffer_queue)->RegisterCallback(bq_player_buffer_queue, &BqPlayerCallback, NULL); assert(SL_RESULT_SUCCESS == result); buffer_size = buf_size; SynthUnit::Init(sample_rate); ring_buffer = new RingBuffer(); stats_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_levien_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 stats_ring_buffer; stats_ring_buffer = NULL; delete synth_unit; synth_unit = NULL; } extern "C" JNIEXPORT void JNICALL Java_com_levien_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_levien_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); } extern "C" JNIEXPORT jint JNICALL Java_com_levien_synthesizer_android_AndroidGlue_statsBytesAvailable( JNIEnv *env, jobject thiz) { return stats_ring_buffer->BytesAvailable(); } extern "C" JNIEXPORT jint JNICALL Java_com_levien_synthesizer_android_AndroidGlue_readStatsBytes( JNIEnv *env, jobject thiz, jbyteArray jb, jint off, jint len) { int bytes_available = stats_ring_buffer->BytesAvailable(); int n = min(bytes_available, len); if (n) { size_t uoff = off; size_t ulen = len; if (off >= 0 && len >= 0 && uoff + ulen <= env->GetArrayLength(jb)) { uint8_t *buf = (uint8_t *)env->GetByteArrayElements(jb, NULL); stats_ring_buffer->Read(n, buf + uoff); env->ReleaseByteArrayElements(jb, (jbyte *)buf, 0); } else { env->ThrowNew(env->FindClass("java/lang/ArrayIndexOutOfBoundsException"), "out of bounds in AndroidGlue.readStatsBytes"); } } return n; }