2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package com.example.android.nativemididemo;
20 import android.app.Activity;
21 import android.content.Context;
22 import android.media.midi.MidiDevice;
23 import android.media.midi.MidiDeviceInfo;
24 import android.media.midi.MidiManager;
25 import android.media.midi.MidiOutputPort;
26 import android.media.midi.MidiReceiver;
27 import android.media.AudioManager;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.view.View;
31 import android.view.WindowManager;
32 import android.widget.RadioButton;
33 import android.widget.RadioGroup;
34 import android.widget.TextView;
36 import java.io.IOException;
38 public class NativeMidi extends Activity
40 private TextView mCallbackStatusTextView;
41 private TextView mJavaMidiStatusTextView;
42 private TextView mMessagesTextView;
43 private RadioGroup mMidiDevicesRadioGroup;
44 private Handler mTimerHandler = new Handler();
45 private boolean mAudioWorks;
46 private final int mMinFramesPerBuffer = 32; // See {min|max}PlaySamples in nativemidi-jni.cpp
47 private final int mMaxFramesPerBuffer = 1000;
48 private int mFramesPerBuffer;
50 private TouchableScrollView mMessagesContainer;
51 private MidiManager mMidiManager;
52 private MidiOutputPortSelector mActivePortSelector;
54 private Runnable mTimerRunnable = new Runnable() {
55 private long mLastTime;
56 private long mLastPlaybackCounter;
57 private int mLastCallbackRate;
58 private long mLastUntouchedTime;
62 final long checkIntervalMs = 1000;
63 long currentTime = System.currentTimeMillis();
64 long currentPlaybackCounter = getPlaybackCounter();
65 if (currentTime - mLastTime >= checkIntervalMs) {
66 int callbackRate = Math.round(
67 (float)(currentPlaybackCounter - mLastPlaybackCounter) /
68 ((float)(currentTime - mLastTime) / (float)1000));
69 if (mLastCallbackRate != callbackRate) {
70 mCallbackStatusTextView.setText(
71 "CB: " + callbackRate + " Hz");
72 mLastCallbackRate = callbackRate;
74 mLastTime = currentTime;
75 mLastPlaybackCounter = currentPlaybackCounter;
78 String[] newMessages = getRecentMessages();
79 if (newMessages != null) {
80 for (String message : newMessages) {
81 mMessagesTextView.append(message);
82 mMessagesTextView.append("\n");
84 if (!mMessagesContainer.isTouched) {
85 if (mLastUntouchedTime == 0) mLastUntouchedTime = currentTime;
86 if (currentTime - mLastUntouchedTime > 3000) {
87 mMessagesContainer.fullScroll(View.FOCUS_DOWN);
90 mLastUntouchedTime = 0;
94 mTimerHandler.postDelayed(this, checkIntervalMs / 4);
98 private void addMessage(final String message) {
99 mTimerHandler.postDelayed(new Runnable() {
102 mMessagesTextView.append(message);
107 private class MidiOutputPortSelector implements View.OnClickListener {
108 private final MidiDeviceInfo mDeviceInfo;
109 private final int mPortNumber;
110 private MidiDevice mDevice;
111 private MidiOutputPort mOutputPort;
113 MidiOutputPortSelector() {
118 MidiOutputPortSelector(MidiDeviceInfo info, int portNumber) {
120 mPortNumber = portNumber;
123 MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; }
126 public void onClick(View v) {
127 if (mActivePortSelector != null) {
128 mActivePortSelector.close();
129 mActivePortSelector = null;
131 if (mDeviceInfo == null) {
132 mActivePortSelector = this;
135 mMidiManager.openDevice(mDeviceInfo, new MidiManager.OnDeviceOpenedListener() {
137 public void onDeviceOpened(MidiDevice device) {
138 if (device == null) {
139 addMessage("! Failed to open MIDI device !\n");
143 mDevice.mirrorToNative();
144 startReadingMidi(mDevice.getInfo().getId(), mPortNumber);
145 } catch (IOException e) {
146 addMessage("! Failed to mirror to native !\n" + e.getMessage() + "\n");
149 mActivePortSelector = MidiOutputPortSelector.this;
151 mOutputPort = device.openOutputPort(mPortNumber);
152 mOutputPort.connect(mMidiReceiver);
158 void closePortOnly() {
165 if (mOutputPort != null) {
168 } catch (IOException e) {
169 mMessagesTextView.append("! Port close error: " + e + "\n");
174 if (mDevice != null) {
177 } catch (IOException e) {
178 mMessagesTextView.append("! Device close error: " + e + "\n");
185 private MidiManager.DeviceCallback mMidiDeviceCallback = new MidiManager.DeviceCallback() {
187 public void onDeviceAdded(MidiDeviceInfo info) {
188 Bundle deviceProps = info.getProperties();
189 String deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_NAME);
190 if (deviceName == null) {
191 deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);
194 for (MidiDeviceInfo.PortInfo port : info.getPorts()) {
195 if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_OUTPUT) continue;
196 String portName = port.getName();
197 int portNumber = port.getPortNumber();
198 if (portName.length() == 0) portName = "[" + portNumber + "]";
199 portName += "@" + deviceName;
200 RadioButton outputDevice = new RadioButton(NativeMidi.this);
201 outputDevice.setText(portName);
202 outputDevice.setTag(info);
203 outputDevice.setOnClickListener(new MidiOutputPortSelector(info, portNumber));
204 mMidiDevicesRadioGroup.addView(outputDevice);
207 NativeMidi.this.updateKeepScreenOn();
211 public void onDeviceRemoved(MidiDeviceInfo info) {
212 if (mActivePortSelector != null && info.equals(mActivePortSelector.getDeviceInfo())) {
213 mActivePortSelector.close();
214 mActivePortSelector = null;
216 int removeButtonStart = -1, removeButtonCount = 0;
217 final int buttonCount = mMidiDevicesRadioGroup.getChildCount();
218 boolean checked = false;
219 for (int i = 0; i < buttonCount; ++i) {
220 RadioButton button = (RadioButton) mMidiDevicesRadioGroup.getChildAt(i);
221 if (!info.equals(button.getTag())) continue;
222 if (removeButtonStart == -1) removeButtonStart = i;
224 if (button.isChecked()) checked = true;
226 if (removeButtonStart != -1) {
227 mMidiDevicesRadioGroup.removeViews(removeButtonStart, removeButtonCount);
229 mMidiDevicesRadioGroup.check(R.id.device_none);
233 NativeMidi.this.updateKeepScreenOn();
237 private class JavaMidiReceiver extends MidiReceiver implements Runnable {
239 public void onSend(byte[] data, int offset,
240 int count, long timestamp) throws IOException {
241 mTimerHandler.removeCallbacks(this);
242 mTimerHandler.postDelayed(new Runnable() {
245 mJavaMidiStatusTextView.setText("Java: MSG");
248 mTimerHandler.postDelayed(this, 100);
253 mJavaMidiStatusTextView.setText("Java: ---");
257 private JavaMidiReceiver mMidiReceiver = new JavaMidiReceiver();
259 private void updateKeepScreenOn() {
260 if (mMidiDevicesRadioGroup.getChildCount() > 1) {
261 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
263 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
268 public void onCreate(Bundle savedInstanceState) {
269 super.onCreate(savedInstanceState);
270 setContentView(R.layout.main);
272 mCallbackStatusTextView = findViewById(R.id.callback_status);
273 mJavaMidiStatusTextView = findViewById(R.id.java_midi_status);
274 mMessagesTextView = findViewById(R.id.messages);
275 mMessagesContainer = findViewById(R.id.messages_scroll);
276 mMidiDevicesRadioGroup = findViewById(R.id.devices);
277 RadioButton deviceNone = findViewById(R.id.device_none);
278 deviceNone.setOnClickListener(new MidiOutputPortSelector());
280 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
281 String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
282 if (sampleRate == null) sampleRate = "48000";
283 String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
284 if (framesPerBuffer == null) framesPerBuffer = Integer.toString(mMaxFramesPerBuffer);
285 mFramesPerBuffer = Integer.parseInt(framesPerBuffer);
286 String audioInitResult = initAudio(Integer.parseInt(sampleRate), mFramesPerBuffer);
287 mMessagesTextView.append("Open SL ES init: " + audioInitResult + "\n");
289 if (audioInitResult.startsWith("Success")) {
291 mTimerHandler.postDelayed(mTimerRunnable, 0);
292 mTimerHandler.postDelayed(mMidiReceiver, 0);
295 mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
296 mMidiManager.registerDeviceCallback(mMidiDeviceCallback, new Handler());
297 for (MidiDeviceInfo info : mMidiManager.getDevices()) {
298 mMidiDeviceCallback.onDeviceAdded(info);
303 public void onPause() {
306 mTimerHandler.removeCallbacks(mTimerRunnable);
312 public void onResume() {
315 mTimerHandler.postDelayed(mTimerRunnable, 0);
321 protected void onDestroy() {
322 if (mActivePortSelector != null) {
323 mActivePortSelector.close();
324 mActivePortSelector = null;
330 public void onClearMessages(View v) {
331 mMessagesTextView.setText("");
334 public void onClosePort(View v) {
335 if (mActivePortSelector != null) {
336 mActivePortSelector.closePortOnly();
340 private native String initAudio(int sampleRate, int playSamples);
341 private native void pauseAudio();
342 private native void resumeAudio();
343 private native void shutdownAudio();
345 private native long getPlaybackCounter();
346 private native String[] getRecentMessages();
348 private native void startReadingMidi(int deviceId, int portNumber);
349 private native void stopReadingMidi();
352 System.loadLibrary("nativemidi_jni");