1#include <atomic>
2#include <inttypes.h>
3#include <stdio.h>
4#include <string.h>
5
6#include <jni.h>
7
8#include <midi/midi.h>
9#include <SLES/OpenSLES.h>
10#include <SLES/OpenSLES_Android.h>
11
12#include "messagequeue.h"
13
14extern "C" {
15JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio(
16        JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples);
17JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
18        JNIEnv* env, jobject thiz);
19JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
20        JNIEnv* env, jobject thiz);
21JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
22        JNIEnv* env, jobject thiz);
23JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(
24        JNIEnv* env, jobject thiz);
25JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
26        JNIEnv* env, jobject thiz);
27JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
28        JNIEnv* env, jobject thiz, jint deviceId, jint portNumber);
29JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
30        JNIEnv* env, jobject thiz);
31}
32
33static const char* errStrings[] = {
34    "SL_RESULT_SUCCESS",                    // 0
35    "SL_RESULT_PRECONDITIONS_VIOLATED",     // 1
36    "SL_RESULT_PARAMETER_INVALID",          // 2
37    "SL_RESULT_MEMORY_FAILURE",             // 3
38    "SL_RESULT_RESOURCE_ERROR",             // 4
39    "SL_RESULT_RESOURCE_LOST",              // 5
40    "SL_RESULT_IO_ERROR",                   // 6
41    "SL_RESULT_BUFFER_INSUFFICIENT",        // 7
42    "SL_RESULT_CONTENT_CORRUPTED",          // 8
43    "SL_RESULT_CONTENT_UNSUPPORTED",        // 9
44    "SL_RESULT_CONTENT_NOT_FOUND",          // 10
45    "SL_RESULT_PERMISSION_DENIED",          // 11
46    "SL_RESULT_FEATURE_UNSUPPORTED",        // 12
47    "SL_RESULT_INTERNAL_ERROR",             // 13
48    "SL_RESULT_UNKNOWN_ERROR",              // 14
49    "SL_RESULT_OPERATION_ABORTED",          // 15
50    "SL_RESULT_CONTROL_LOST" };             // 16
51static const char* getSLErrStr(int code) {
52    return errStrings[code];
53}
54
55static SLObjectItf engineObject;
56static SLEngineItf engineEngine;
57static SLObjectItf outputMixObject;
58static SLObjectItf playerObject;
59static SLPlayItf playerPlay;
60static SLAndroidSimpleBufferQueueItf playerBufferQueue;
61
62static const int minPlaySamples = 32;
63static const int maxPlaySamples = 1000;
64static std::atomic_int playSamples(maxPlaySamples);
65static short playBuffer[maxPlaySamples];
66
67static std::atomic_ullong sharedCounter;
68
69static AMIDI_Device* midiDevice = AMIDI_INVALID_HANDLE;
70static std::atomic<AMIDI_OutputPort*> midiOutputPort(AMIDI_INVALID_HANDLE);
71
72static int setPlaySamples(int newPlaySamples)
73{
74    if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples;
75    if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples;
76    playSamples.store(newPlaySamples);
77    return newPlaySamples;
78}
79
80// Amount of messages we are ready to handle during one callback cycle.
81static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
82// Static allocation to save time in the callback.
83static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
84
85static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
86{
87    sharedCounter++;
88
89    AMIDI_OutputPort* outputPort = midiOutputPort.load();
90    if (outputPort != AMIDI_INVALID_HANDLE) {
91        char midiDumpBuffer[1024];
92        ssize_t midiReceived = AMIDI_receive(
93                outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES);
94        if (midiReceived >= 0) {
95            for (ssize_t i = 0; i < midiReceived; ++i) {
96                AMIDI_Message* msg = &incomingMidiMessages[i];
97                if (msg->opcode == AMIDI_OPCODE_DATA) {
98                    memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
99                    int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
100                            "%" PRIx64 " ", msg->timestamp);
101                    for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) {
102                        pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
103                                "%02x ", *b);
104                    }
105                    nativemididemo::writeMessage(midiDumpBuffer);
106                } else if (msg->opcode == AMIDI_OPCODE_FLUSH) {
107                    nativemididemo::writeMessage("MIDI flush");
108                }
109            }
110        } else {
111            snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
112                    "! MIDI Receive error: %s !", strerror(-midiReceived));
113            nativemididemo::writeMessage(midiDumpBuffer);
114        }
115    }
116
117    size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]);
118    if (usedBufferSize > sizeof(playBuffer)) {
119        usedBufferSize = sizeof(playBuffer);
120    }
121    (*bq)->Enqueue(bq, playBuffer, usedBufferSize);
122}
123
124jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio(
125        JNIEnv* env, jobject, jint sampleRate, jint playSamples) {
126    const char* stage;
127    SLresult result;
128    char printBuffer[1024];
129
130    playSamples = setPlaySamples(playSamples);
131
132    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
133    if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; }
134
135    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
136    if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; }
137
138    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
139    if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; }
140
141    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
142    if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; }
143
144    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
145    if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; }
146
147    {
148    SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000,
149                                    SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
150                                    SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN };
151    SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
152            { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
153    SLDataSource audioSrc = { &loc_bufq, &format_pcm };
154    SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
155    SLDataSink audioSnk = { &loc_outmix, NULL };
156    const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE };
157    const SLboolean req[1] = { SL_BOOLEAN_TRUE };
158    result = (*engineEngine)->CreateAudioPlayer(
159            engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req);
160    if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; }
161
162    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
163    if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; }
164    }
165
166    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
167    if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; }
168
169    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
170    if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; }
171
172    result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL);
173    if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; }
174
175    result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
176    if (SL_RESULT_SUCCESS != result) {
177        stage = "enqueue into PlayerBufferQueue"; goto handle_error; }
178
179    result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
180    if (SL_RESULT_SUCCESS != result) {
181        stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; }
182
183    snprintf(printBuffer, sizeof(printBuffer),
184            "Success, sample rate %d, buffer samples %d", sampleRate, playSamples);
185    return env->NewStringUTF(printBuffer);
186
187handle_error:
188    snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result));
189    return env->NewStringUTF(printBuffer);
190}
191
192void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
193        JNIEnv*, jobject) {
194    if (playerPlay != NULL) {
195        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
196    }
197}
198
199void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
200        JNIEnv*, jobject) {
201    if (playerBufferQueue != NULL && playerPlay != NULL) {
202        (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
203        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
204    }
205}
206
207void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
208        JNIEnv*, jobject) {
209    if (playerObject != NULL) {
210        (*playerObject)->Destroy(playerObject);
211        playerObject = NULL;
212        playerPlay = NULL;
213        playerBufferQueue = NULL;
214    }
215
216    if (outputMixObject != NULL) {
217        (*outputMixObject)->Destroy(outputMixObject);
218        outputMixObject = NULL;
219    }
220
221    if (engineObject != NULL) {
222        (*engineObject)->Destroy(engineObject);
223        engineObject = NULL;
224        engineEngine = NULL;
225    }
226}
227
228jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) {
229    return sharedCounter.load();
230}
231
232jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
233        JNIEnv* env, jobject thiz) {
234    return nativemididemo::getRecentMessagesForJava(env, thiz);
235}
236
237void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
238        JNIEnv*, jobject, jlong deviceHandle, jint portNumber) {
239    char buffer[1024];
240
241    midiDevice = (AMIDI_Device*)deviceHandle;
242//    int result = AMIDI_getDeviceById(deviceId, &midiDevice);
243//    if (result == 0) {
244//        snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice);
245//    } else {
246//        snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result);
247//    }
248    nativemididemo::writeMessage(buffer);
249//    if (result) return;
250
251    AMIDI_DeviceInfo deviceInfo;
252    int result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo);
253    if (result == 0) {
254        snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O",
255                deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate,
256                (int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount);
257    } else {
258        snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result);
259    }
260    nativemididemo::writeMessage(buffer);
261    if (result) return;
262
263    AMIDI_OutputPort* outputPort;
264    result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort);
265    if (result == 0) {
266        snprintf(buffer, sizeof(buffer), "Opened port %d: token %p", portNumber, outputPort);
267        midiOutputPort.store(outputPort);
268    } else {
269        snprintf(buffer, sizeof(buffer), "Could not open port %p: %d", midiDevice, result);
270    }
271    nativemididemo::writeMessage(buffer);
272}
273
274void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
275        JNIEnv*, jobject) {
276    AMIDI_OutputPort* outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE);
277    if (outputPort == AMIDI_INVALID_HANDLE) return;
278    int result = AMIDI_closeOutputPort(outputPort);
279    char buffer[1024];
280    if (result == 0) {
281        snprintf(buffer, sizeof(buffer), "Closed port by token %p", outputPort);
282    } else {
283        snprintf(buffer, sizeof(buffer), "Could not close port by token %p: %d", outputPort, result);
284    }
285    nativemididemo::writeMessage(buffer);
286}
287