Skip to content

Commit a0d36a0

Browse files
committed
android: add an example
1 parent c568cb3 commit a0d36a0

File tree

13 files changed

+657
-0
lines changed

13 files changed

+657
-0
lines changed

examples/android/app/build.gradle

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
apply plugin: 'com.android.application'
2+
3+
android {
4+
compileSdk 34
5+
ndkVersion "25.2.9519653"
6+
7+
defaultConfig {
8+
applicationId "com.libremidi.example"
9+
minSdk 31
10+
targetSdk 34
11+
versionCode 1
12+
versionName "1.0"
13+
14+
externalNativeBuild {
15+
cmake {
16+
cppFlags "-std=c++20 -frtti -fexceptions"
17+
arguments "-DANDROID_STL=c++_shared"
18+
}
19+
}
20+
ndk {
21+
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
22+
}
23+
}
24+
25+
buildTypes {
26+
release {
27+
minifyEnabled false
28+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29+
}
30+
}
31+
32+
externalNativeBuild {
33+
cmake {
34+
path "src/main/cpp/CMakeLists.txt"
35+
version "3.22.1"
36+
}
37+
}
38+
39+
compileOptions {
40+
sourceCompatibility JavaVersion.VERSION_11
41+
targetCompatibility JavaVersion.VERSION_11
42+
}
43+
44+
namespace 'com.libremidi.example'
45+
}
46+
47+
dependencies {
48+
implementation 'androidx.appcompat:appcompat:1.6.1'
49+
implementation 'com.google.android.material:material:1.11.0'
50+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
51+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-feature android:name="android.software.midi" android:required="true" />
5+
<uses-permission android:name="android.permission.BIND_MIDI_DEVICE_SERVICE" />
6+
7+
<application
8+
android:allowBackup="true"
9+
android:icon="@android:drawable/ic_menu_manage"
10+
android:label="LibreMIDI Example"
11+
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
12+
13+
<activity
14+
android:name=".MainActivity"
15+
android:exported="true">
16+
<intent-filter>
17+
<action android:name="android.intent.action.MAIN" />
18+
<category android:name="android.intent.category.LAUNCHER" />
19+
</intent-filter>
20+
</activity>
21+
22+
</application>
23+
24+
</manifest>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
cmake_minimum_required(VERSION 3.22.1)
2+
project(libremidi-example)
3+
4+
set(CMAKE_CXX_STANDARD 20)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
7+
# Find libremidi in the parent directory
8+
set(LIBREMIDI_NO_ANDROID 0)
9+
set(BUILD_SHARED_LIBS 1)
10+
set(LIBREMIDI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../..")
11+
add_subdirectory(${LIBREMIDI_ROOT} libremidi-build)
12+
13+
# Create shared library
14+
add_library(libremidi-example SHARED
15+
native-lib.cpp
16+
)
17+
18+
# Link libremidi
19+
target_link_libraries(libremidi-example
20+
libremidi
21+
android
22+
log
23+
)
24+
25+
# Include directories
26+
target_include_directories(libremidi-example PRIVATE
27+
${LIBREMIDI_ROOT}/include
28+
)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include <jni.h>
2+
#include <string>
3+
#include <vector>
4+
#include <sstream>
5+
#include <libremidi/libremidi.hpp>
6+
7+
#include <android/log.h>
8+
extern "C" JNIEXPORT jstring JNICALL
9+
Java_com_libremidi_example_MainActivity_getMidiDevices(JNIEnv* env, jobject /* this */) {
10+
std::stringstream result;
11+
__android_log_print(ANDROID_LOG_WARN, "libremidi", "Java_com_libremidi_example_MainActivity_getMidiDevices!!");
12+
13+
try {
14+
auto api = libremidi::API::ANDROID_AMIDI;
15+
libremidi::observer obs{{.track_any = true}, libremidi::observer_configuration_for(api)};
16+
17+
result << "=== MIDI Input Devices ===\n";
18+
auto inputs = obs.get_input_ports();
19+
if (inputs.empty()) {
20+
result << "No input devices found\n";
21+
} else {
22+
for (const auto& port : inputs) {
23+
result << "Port " << port.port << ": " << port.port_name;
24+
if (!port.device_name.empty() && port.device_name != port.port_name) {
25+
result << " (Device: " << port.device_name << ")";
26+
}
27+
if (!port.display_name.empty() && port.display_name != port.port_name) {
28+
result << " [" << port.display_name << "]";
29+
}
30+
result << "\n";
31+
32+
__android_log_print(ANDROID_LOG_WARN, "libremidi", "opening: %s", port.port_name.c_str());
33+
auto midi_in = new libremidi::midi_in{{.on_message{[](const libremidi::message& m) {
34+
__android_log_print(ANDROID_LOG_WARN, "libremidi", "ayy!!");
35+
}}}, libremidi::midi_in_configuration_for(api)};
36+
37+
auto p = midi_in->get_current_api();
38+
39+
__android_log_print(ANDROID_LOG_WARN, "libremidi", ">> ??! %s", libremidi::get_api_name(p).data());
40+
__android_log_print(ANDROID_LOG_WARN, "libremidi", ">> open port?!");
41+
midi_in->open_port(port);
42+
}
43+
}
44+
45+
result << "\n=== MIDI Output Devices ===\n";
46+
auto outputs = obs.get_output_ports();
47+
if (outputs.empty()) {
48+
result << "No output devices found\n";
49+
} else {
50+
for (const auto& port : outputs) {
51+
result << "Port " << port.port << ": " << port.port_name;
52+
if (!port.device_name.empty() && port.device_name != port.port_name) {
53+
result << " (Device: " << port.device_name << ")";
54+
}
55+
if (!port.display_name.empty() && port.display_name != port.port_name) {
56+
result << " [" << port.display_name << "]";
57+
}
58+
result << "\n";
59+
}
60+
}
61+
62+
} catch (const std::exception& e) {
63+
result << "Error: " << e.what() << "\n";
64+
}
65+
66+
__android_log_print(ANDROID_LOG_WARN, "libremidi", "FINITO!!");
67+
return env->NewStringUTF(result.str().c_str());
68+
}
69+
70+
extern "C" JNIEXPORT jstring JNICALL
71+
Java_com_libremidi_example_MainActivity_getLibremidiVersion(JNIEnv* env, jobject /* this */) {
72+
return env->NewStringUTF("libremidi Android example");
73+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.libremidi.example;
2+
3+
import androidx.appcompat.app.AppCompatActivity;
4+
import android.os.Bundle;
5+
import android.widget.TextView;
6+
import android.widget.Button;
7+
import android.widget.ScrollView;
8+
import android.view.ViewGroup.LayoutParams;
9+
import android.widget.LinearLayout;
10+
import android.graphics.Typeface;
11+
import android.util.TypedValue;
12+
13+
public class MainActivity extends AppCompatActivity {
14+
15+
static {
16+
System.loadLibrary("libremidi-example");
17+
}
18+
19+
private TextView deviceListTextView;
20+
21+
@Override
22+
protected void onCreate(Bundle savedInstanceState) {
23+
super.onCreate(savedInstanceState);
24+
25+
LinearLayout layout = new LinearLayout(this);
26+
layout.setOrientation(LinearLayout.VERTICAL);
27+
layout.setPadding(16, 16, 16, 16);
28+
29+
TextView titleView = new TextView(this);
30+
titleView.setText("LibreMIDI Android Example");
31+
titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
32+
titleView.setTypeface(null, Typeface.BOLD);
33+
titleView.setPadding(0, 0, 0, 16);
34+
layout.addView(titleView);
35+
36+
TextView versionView = new TextView(this);
37+
versionView.setText(getLibremidiVersion());
38+
versionView.setPadding(0, 0, 0, 16);
39+
layout.addView(versionView);
40+
41+
Button refreshButton = new Button(this);
42+
refreshButton.setText("Refresh MIDI Devices");
43+
refreshButton.setOnClickListener(v -> refreshDevices());
44+
layout.addView(refreshButton);
45+
46+
ScrollView scrollView = new ScrollView(this);
47+
deviceListTextView = new TextView(this);
48+
deviceListTextView.setTypeface(Typeface.MONOSPACE);
49+
deviceListTextView.setPadding(0, 16, 0, 0);
50+
scrollView.addView(deviceListTextView);
51+
52+
LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams(
53+
LayoutParams.MATCH_PARENT,
54+
LayoutParams.MATCH_PARENT
55+
);
56+
scrollParams.weight = 1;
57+
layout.addView(scrollView, scrollParams);
58+
59+
setContentView(layout);
60+
61+
refreshDevices();
62+
}
63+
64+
private void refreshDevices() {
65+
String devices = getMidiDevices();
66+
deviceListTextView.setText(devices);
67+
}
68+
69+
public native String getMidiDevices();
70+
public native String getLibremidiVersion();
71+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dev.celtera.libremidi;
2+
3+
import android.media.midi.MidiDevice;
4+
import android.media.midi.MidiManager;
5+
import android.util.Log;
6+
7+
public class MidiDeviceCallback implements MidiManager.OnDeviceOpenedListener {
8+
private static final String TAG = "libremidi";
9+
private long nativePtr;
10+
private boolean isOutput;
11+
12+
public MidiDeviceCallback(long ptr, boolean output) {
13+
nativePtr = ptr;
14+
isOutput = output;
15+
}
16+
@Override
17+
public void onDeviceOpened(MidiDevice device) {
18+
if (device == null) {
19+
Log.e(TAG, "Failed to open MIDI device");
20+
return;
21+
}
22+
23+
Log.i(TAG, "MIDI device opened successfully");
24+
onDeviceOpened(device, nativePtr, isOutput);
25+
}
26+
27+
// Native method declaration
28+
private native void onDeviceOpened(MidiDevice device, long targetPtr, boolean isOutput);
29+
30+
static {
31+
// Load the native library containing the JNI implementation
32+
// System.loadLibrary("remidi");
33+
}
34+
}

examples/android/build.gradle

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
buildscript {
2+
repositories {
3+
google()
4+
mavenCentral()
5+
}
6+
dependencies {
7+
classpath 'com.android.tools.build:gradle:8.11.1'
8+
}
9+
}
10+
11+
allprojects {
12+
repositories {
13+
google()
14+
mavenCentral()
15+
}
16+
}
17+
18+
task clean(type: Delete) {
19+
delete rootProject.buildDir
20+
}

examples/android/gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
2+
android.useAndroidX=true
3+
android.nonTransitiveRClass=true
42.7 KB
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-milestone-1-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
6+
zipStoreBase=GRADLE_USER_HOME
7+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)