Skip to content

Instantly share code, notes, and snippets.

@yoggy
Created September 27, 2025 07:51
Show Gist options
  • Save yoggy/e5f1844e1fba02d6e73da8bca2a43d86 to your computer and use it in GitHub Desktop.
Save yoggy/e5f1844e1fba02d6e73da8bca2a43d86 to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.AndroidMidiTest"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-feature android:name="android.software.midi" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
</manifest>
package net.sabamiso.android.androidmiditest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceStatus;
import android.media.midi.MidiInputPort;
import android.media.midi.MidiManager;
import android.media.midi.MidiOutputPort;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.util.Log;
import android.widget.Toast;
import android.Manifest;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
//
// AndroidからKORG nanoKey StudioへBLEで接続するテスト
//
// 注意点:
// nanoKey Studioのペアリングは、アプリ初回起動時にアプリ内で実施すること。
// アプリを起動する前に先にシステム設定からペアリングしてしまうと、なぜかnanoKey Studioが使えなくなる現象あり
//
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private MidiManager midiManager;
private MidiDevice midiDevice;
private MidiOutputPort outputPort;
private final static int REQUEST_CODE = 123;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// MidiManagerのインスタンスを取得
midiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
if (midiManager == null) {
Toast.makeText(this, "midiManager == null...", Toast.LENGTH_SHORT).show();
return;
}
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION , Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT}, REQUEST_CODE);
}
else {
startScan();
}
}
@Override
protected void onPause() {
super.onPause();
if(midiDevice != null) {
try {
midiDevice.close();
midiDevice = null;
} catch(IOException e) {
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startScan();
}
}
}
static final UUID MIDI_SERVICE_UUID = UUID.fromString("03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
@SuppressLint("MissingPermission")
void startScan() {
Log.d(TAG, "startScan()");
ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
ScanFilter uuidFilter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(MIDI_SERVICE_UUID)).build();
List<ScanFilter> scanFilters = new ArrayList<>();
scanFilters.add(uuidFilter);
BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().startScan(scanFilters, settings, mScanCallback);
}
@SuppressLint("MissingPermission")
void stopScan() {
Log.d(TAG, "stopScan()");
BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().stopScan(mScanCallback);
}
ScanCallback mScanCallback =
new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
bluetoothDevice = result.getDevice();
Log.d(TAG, "find BluetoothDevice");
stopScan();
setupMidi();
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
if(0 < results.size()){
bluetoothDevice = results.get(0).getDevice();
Log.d(TAG, "find BluetoothDevice");
}
stopScan();
setupMidi();
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
stopScan();
}
};
BluetoothDevice bluetoothDevice = null;
private void setupMidi() {
midiManager.registerDeviceCallback(mDeviceCallback , new Handler(Looper.getMainLooper()));
midiManager.openBluetoothDevice(bluetoothDevice, new MidiManager.OnDeviceOpenedListener() {
@Override
public void onDeviceOpened(MidiDevice device) {
Log.d(TAG, "onDeviceOpened()");
if (bluetoothDevice != null) {
waitForPairing(device);
}
}
}, new Handler(Looper.getMainLooper()));
}
@SuppressLint("MissingPermission")
private void waitForPairing(MidiDevice device)
{
if (bluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
Log.d(TAG, bluetoothDevice.getName() + " is BOND_BONDED");
setupOutputPort(device);
} else {
// 初回起動時&初回ペアリング時は、ペアリングがまだできていないので、ペアリング済み状態になるまで待つ
Log.d(TAG, bluetoothDevice.getName() + " is not BOND_BONDED...");
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
waitForPairing(device);
}
}, 1000);
}
}
private void setupOutputPort(MidiDevice device) {
if(device.getInfo().getOutputPortCount() > 0){
Log.d(TAG, "device.getInfo().getOutputPortCount() == "+device.getInfo().getOutputPortCount());
midiDevice = device;
MidiOutputPort outputPort = device.openOutputPort(0);
outputPort.connect(new MyMidiReceiver());
}
}
private MidiManager.DeviceCallback mDeviceCallback = new MidiManager.DeviceCallback() {
@Override
public void onDeviceAdded(MidiDeviceInfo device) {
Log.d(TAG, "onDeviceAdded() " + device.toString());
}
@Override
public void onDeviceRemoved(MidiDeviceInfo device) {
Log.d(TAG, "onDeviceRemoved() " + device.toString());
}
@Override
public void onDeviceStatusChanged(MidiDeviceStatus status) {
Log.d(TAG, "onDeviceStatusChanged() " + status.toString());
}
};
@Override
protected void onDestroy() {
super.onDestroy();
stopScan();
try {
if (outputPort != null) {
outputPort.close();
}
if (midiDevice != null) {
midiDevice.close();
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}
package net.sabamiso.android.androidmiditest;
import android.media.midi.MidiReceiver;
import android.util.Log;
import java.io.IOException;
public class MyMidiReceiver extends MidiReceiver {
private static final String TAG = "MyMidiReceiver";
@Override
public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
byte statusByte = msg[offset];
int command = statusByte & 0xf0;
int channel = statusByte & 0x0f;
switch (command) {
case 0x90:
Log.d(TAG, String.format("0x90 : note on : channel=%d, note=%d, velocity=%d", channel, msg[offset + 1], msg[offset + 2]));
break;
case 0x80:
Log.d(TAG, String.format("0x80 : note off : channel=%d, note=%d, velocity=%d", channel, msg[offset + 1], msg[offset + 2]));
break;
case 0xb0:
Log.d(TAG, String.format("0xb0 : controll change : channel=%d, cc=%d, value=%d", channel, msg[offset + 1], msg[offset + 2]));
break;
case 0xc0:
Log.d(TAG, String.format("0xc0 : program change : channel=%d, pc=%d, value=%d", channel, msg[offset + 1], msg[offset + 2]));
break;
case 0xe0:
// program change
Log.d(TAG, String.format("0xe0 : pitch bend : channel=%d, value1=%d, value2=%d", channel, msg[offset + 1], msg[offset + 2]));
break;
case 0xf0:
// system exclusive
break;
case 0xf7:
// system exclusive
break;
case 0xff:
// meta event
break;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment