AMidi API, Android NDK r20b ve sonraki sürümlerde kullanılabilir. Uygulama geliştiricilere C/C++ koduyla MIDI verilerini gönderme ve alma olanağı tanır.
Android MIDI uygulamaları, Android MIDI hizmetiyle iletişim kurmak için genellikle midi
API'sini kullanır. MIDI uygulamaları, öncelikle bir veya daha fazla MidiDevice
nesneyi keşfetmek, açmak ve kapatmak için MidiManager
'ye bağlıdır. Ayrıca cihazın MIDI giriş ve çıkış bağlantı noktaları aracılığıyla her cihaza veri iletir ve her cihazdan veri alır:
AMidi'yi kullandığınızda, JNI çağrısıyla MidiDevice
adresini yerel kod katmanına iletirsiniz. AMidi, bu noktadan itibaren AMidiDevice
bir MidiDevice
'nin işlevlerinin çoğunu içeren bir referans oluşturur. Yerel kodunuz, doğrudan AMidiDevice
ile iletişim kuran AMidi işlevlerini kullanıyor. AMidiDevice
, doğrudan MIDI hizmetine bağlanıyor:
AMidi çağrılarını kullanarak uygulamanızın C/C++ ses/kontrol mantığını MIDI iletimiyle yakından entegre edebilirsiniz. JNI çağrılarına veya uygulamanızın Java tarafına geri çağırmalara daha az ihtiyaç duyulur. Örneğin, C koduyla uygulanan bir dijital sentezleyici, tuş etkinliklerini Java tarafındaki JNI çağrısının etkinlikleri göndermesini beklemek yerine doğrudan AMidiDevice
'dan alabilir. Alternatif olarak, algoritmik bir beste süreci, önemli etkinlikleri iletmek için Java tarafına geri çağırmadan doğrudan bir AMidiDevice
'ye MIDI performansı gönderebilir.
AMidi, MIDI cihazlarıyla doğrudan bağlantıyı iyileştirse de uygulamaların MidiManager
nesnelerini keşfetmek ve açmak için MidiDevice
kullanmaya devam etmesi gerekir. AMidi bu noktadan sonra devam edebilir.
Bazen kullanıcı arayüzü katmanındaki bilgileri yerel koda geçirmeniz gerekebilir. Örneğin, ekrandaki düğmelere yanıt olarak MIDI etkinlikleri gönderildiğinde. Bunu yapmak için yerel mantığınıza özel JNI çağrıları oluşturun. Kullanıcı arayüzünü güncellemek için veri göndermeniz gerekiyorsa her zamanki gibi yerel katmandan geri arama yapabilirsiniz.
Bu belgede, AMidi yerel kod uygulamasının nasıl ayarlanacağı gösterilmekte, hem MIDI komutlarının gönderilmesi hem de alınmasıyla ilgili örnekler verilmektedir. Çalışan tam bir örnek için NativeMidi örnek uygulamasına göz atın.
AMidi'yi kullanma
AMidi kullanan tüm uygulamalar, MIDI gönderip göndermediklerine, alıp almadıklarına veya her ikisini de yapıp yapmadıklarına bakılmaksızın aynı kurulum ve kapatma adımlarına sahiptir.
AMidi'yi başlatma
Java tarafında, uygulamanın bağlı bir MIDI donanımını bulması, buna karşılık gelen bir MidiDevice
oluşturması ve bunu yerel koda iletmesi gerekir.
- Java
MidiManager
sınıfıyla MIDI donanımını keşfedin. - MIDI donanımına karşılık gelen bir Java
MidiDevice
nesnesi edinin. - Java
MidiDevice
öğesini JNI ile yerel koda aktarın.
Donanımı ve bağlantı noktalarını keşfetme
Giriş ve çıkış bağlantı noktası nesneleri uygulamaya ait değildir. Bunlar midi cihazındaki bağlantı noktalarını temsil eder. Bir uygulamada MIDI verilerini cihaza göndermek için MIDIInputPort
açılır ve veriler buraya yazılır. Aksine, veri almak için bir uygulama MIDIOutputPort
açar. Uygulamanın düzgün çalışması için açtığı bağlantı noktalarının doğru türde olduğundan emin olması gerekir. Cihaz ve bağlantı noktası keşfi Java tarafında yapılır.
Her MIDI cihazını keşfeden ve bağlantı noktalarına bakan bir yöntem aşağıda verilmiştir. Veri almak için çıkış bağlantı noktaları olan cihazların listesini veya veri göndermek için giriş bağlantı noktaları olan cihazların listesini döndürür. Bir MIDI cihazında hem giriş bağlantı noktaları hem de çıkış bağlantı noktaları olabilir.
Kotlin
private fun getMidiDevices(isOutput: Boolean) : List{ if (isOutput) { return mMidiManager.devices.filter { it.outputPortCount > 0 } } else { return mMidiManager.devices.filter { it.inputPortCount > 0 } } }
Java
private ListgetMidiDevices(boolean isOutput){ ArrayList filteredMidiDevices = new ArrayList<>(); for (MidiDeviceInfo midiDevice : mMidiManager.getDevices()){ if (isOutput){ if (midiDevice.getOutputPortCount() > 0) filteredMidiDevices.add(midiDevice); } else { if (midiDevice.getInputPortCount() > 0) filteredMidiDevices.add(midiDevice); } } return filteredMidiDevices; }
C/C++ kodunuzda AMidi işlevlerini kullanmak için AMidi/AMidi.h
dosyasını eklemeniz ve amidi
kitaplığına bağlamanız gerekir. Bunların her ikisi de Android NDK'da bulunabilir.
Java tarafı, JNI çağrısı aracılığıyla yerel katmana bir veya daha fazla MidiDevice
nesnesi ve bağlantı noktası numarası iletmelidir. Yerel katman daha sonra aşağıdaki adımları uygulamalıdır:
- Her Java
MidiDevice
içinAMidiDevice_fromJava()
kullanarakAMidiDevice
elde edin. AMidiInputPort_open()
ve/veyaAMidiOutputPort_open()
ile birlikteAMidiDevice
üzerindenAMidiInputPort
ve/veyaAMidiOutputPort
edinin.- Elde edilen bağlantı noktalarını MIDI verilerini göndermek ve/veya almak için kullanın.
Stop AMidi
Java uygulaması, MIDI cihazını kullanmayı bıraktığında kaynakları serbest bırakması için yerel katmana sinyal göndermelidir. Bunun nedeni, MIDI cihazının bağlantısının kesilmesi veya uygulamanın çıkış yapması olabilir.
MIDI kaynaklarını serbest bırakmak için kodunuz şu görevleri gerçekleştirmelidir:
- MIDI bağlantı noktalarına okuma ve/veya yazma işlemlerini durdurun. Giriş için anket yapmak üzere bir okuma iş parçacığı kullanıyorsanız (aşağıdaki Anket döngüsü uygulama bölümüne bakın) iş parçacığını durdurun.
AMidiInputPort
ve/veyaAMidiOutputPort
işlevleriyle açık olanAMidiInputPort_close()
ve/veyaAMidiOutputPort_close()
nesnelerini kapatın.AMidiDevice_release()
ileAMidiDevice
öğesini yayınlayın.
MIDI verilerini alma
MIDI alan bir MIDI uygulamasının tipik bir örneği, ses sentezini kontrol etmek için MIDI performans verilerini alan bir "sanal sentezleyici"dir.
Gelen MIDI verileri eşzamansız olarak alınır. Bu nedenle, MIDI'yi sürekli olarak bir veya daha fazla MIDI çıkış bağlantı noktasını yoklayan ayrı bir iş parçacığında okumak en iyisidir. Bu, arka plan iş parçacığı veya ses iş parçacığı olabilir. AMidi, bir bağlantı noktasından okuma yaparken engelleme yapmaz. Bu nedenle, bir ses geri çağırma işlevi içinde güvenle kullanılabilir.
MidiDevice ve çıkış bağlantı noktalarını ayarlama
Bir uygulama, cihazın çıkış bağlantı noktalarından gelen MIDI verilerini okur. Uygulamanızın Java tarafı, hangi cihazın ve bağlantı noktalarının kullanılacağını belirlemelidir.
Bu snippet, Android'in MIDI hizmetinden MidiManager
oluşturur ve bulduğu ilk cihaz için MidiDevice
açar. MidiDevice
açıldığında MidiManager.OnDeviceOpenedListener()
örneğine geri çağırma alınır. Bu dinleyicinin onDeviceOpened
yöntemi çağrılır ve bu yöntem de cihazda çıkış bağlantı noktası 0'ı açmak için startReadingMidi()
yöntemini çağırır. Bu, AppMidiManager.cpp
içinde tanımlanan bir JNI işlevidir. Bu işlev, sonraki snippet'te açıklanmaktadır.
Kotlin
//AppMidiManager.kt class AppMidiManager(context : Context) { private external fun startReadingMidi(midiDevice: MidiDevice, portNumber: Int) val mMidiManager : MidiManager = context.getSystemService(Context.MIDI_SERVICE) as MidiManager init { val midiDevices = getMidiDevices(true) // method defined in snippet above if (midiDevices.isNotEmpty()){ midiManager.openDevice(midiDevices[0], { startReadingMidi(it, 0) }, null) } } }
Java
//AppMidiManager.java public class AppMidiManager { private native void startReadingMidi(MidiDevice device, int portNumber); private MidiManager mMidiManager; AppMidiManager(Context context){ mMidiManager = (MidiManager) context.getSystemService(Context.MIDI_SERVICE); ListmidiDevices = getMidiDevices(true); // method defined in snippet above if (midiDevices.size() > 0){ mMidiManager.openDevice(midiDevices.get(0), new MidiManager.OnDeviceOpenedListener() { @Override public void onDeviceOpened(MidiDevice device) { startReadingMidi(device, 0); } },null); } } }
Yerel kod, Java tarafındaki MIDI cihazını ve bağlantı noktalarını AMidi işlevleri tarafından kullanılan referanslara çevirir.
AMidiDevice
çağrılarak AMidiDevice_fromJava()
oluşturulan ve ardından cihazda bir çıkış bağlantı noktasını açmak için AMidiOutputPort_open()
çağrısı yapılan JNI işlevi aşağıda verilmiştir:
AppMidiManager.cpp
AMidiDevice midiDevice;
static pthread_t readThread;
static const AMidiDevice* midiDevice = AMIDI_INVALID_HANDLE;
static std::atomic<AMidiOutputPort*> midiOutputPort(AMIDI_INVALID_HANDLE);
void Java_com_nativemidiapp_AppMidiManager_startReadingMidi(
JNIEnv* env, jobject, jobject deviceObj, jint portNumber) {
AMidiDevice_fromJava(j_env, deviceObj, &midiDevice);
AMidiOutputPort* outputPort;
int32_t result =
AMidiOutputPort_open(midiDevice, portNumber, &outputPort);
// check for errors...
// Start read thread
int pthread_result =
pthread_create(&readThread, NULL, readThreadRoutine, NULL);
// check for errors...
}
Yoklama döngüsü uygulama
MIDI verilerini alan uygulamalar, çıkış bağlantı noktasını yoklamalı ve AMidiOutputPort_receive()
sıfırdan büyük bir sayı döndürdüğünde yanıt vermelidir.
MIDI kapsamı gibi düşük bant genişliğine sahip uygulamalarda, düşük öncelikli bir arka plan iş parçacığında (uygun uyku süreleriyle) yoklama yapabilirsiniz.
Ses üreten ve daha katı gerçek zamanlı performans gereksinimleri olan uygulamalar için ana ses üretme geri çağırmasında (OpenSL ES için BufferQueue
geri çağırması, AAudio'da AudioStream veri geri çağırması) yoklama yapabilirsiniz.
AMidiOutputPort_receive()
engelleme yapmadığı için performans üzerindeki etkisi çok düşüktür.
Yukarıdaki startReadingMidi()
işlevinden çağrılan readThreadRoutine()
işlevi aşağıdaki gibi görünebilir:
void* readThreadRoutine(void * /*context*/) {
uint8_t inDataBuffer[SIZE_DATABUFFER];
int32_t numMessages;
uint32_t opCode;
uint64_t timestamp;
reading = true;
while (reading) {
AMidiOutputPort* outputPort = midiOutputPort.load();
numMessages =
AMidiOutputPort_receive(outputPort, &opCode, inDataBuffer,
sizeof(inDataBuffer), ×tamp);
if (numMessages >= 0) {
if (opCode == AMIDI_OPCODE_DATA) {
// Dispatch the MIDI data….
}
} else {
// some error occurred, the negative numMessages is the error code
int32_t errorCode = numMessages;
}
}
}
Yerel bir ses API'si (ör. OpenSL ES veya AAudio) kullanan bir uygulama, ses oluşturma geri çağırmasına aşağıdaki gibi bir MIDI alma kodu ekleyebilir:
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
{
uint8_t inDataBuffer[SIZE_DATABUFFER];
int32_t numMessages;
uint32_t opCode;
uint64_t timestamp;
// Read MIDI Data
numMessages = AMidiOutputPort_receive(outputPort, &opCode, inDataBuffer,
sizeof(inDataBuffer), ×tamp);
if (numMessages >= 0 && opCode == AMIDI_OPCODE_DATA) {
// Parse and respond to MIDI data
// ...
}
// Generate Audio…
// ...
}
Aşağıdaki şemada, MIDI okuma uygulamasının akışı gösterilmektedir:
MIDI verilerini gönderme
MIDI yazma uygulamalarına örnek olarak MIDI denetleyiciler veya sıralayıcılar verilebilir.
MidiDevice ve giriş bağlantı noktalarını ayarlama
Bir uygulama, giden MIDI verilerini MIDI cihazının giriş bağlantı noktalarına yazar. Uygulamanızın Java tarafı, hangi MIDI cihazının ve bağlantı noktalarının kullanılacağını belirlemelidir.
Aşağıdaki kurulum kodu, yukarıdaki alma örneğinin bir varyasyonudur. Android'in MIDI hizmetinden MidiManager
oluşturur. Ardından, bulduğu ilkMidiDevice
öğesini açar ve cihazdaki ilk giriş bağlantı noktasını açmak için startWritingMidi()
işlevini çağırır. Bu, AppMidiManager.cpp
içinde tanımlanan bir JNI çağrısıdır. İşlev, sonraki snippet'te açıklanmaktadır.
Kotlin
//AppMidiManager.kt class AppMidiManager(context : Context) { private external fun startWritingMidi(midiDevice: MidiDevice, portNumber: Int) val mMidiManager : MidiManager = context.getSystemService(Context.MIDI_SERVICE) as MidiManager init { val midiDevices = getMidiDevices(false) // method defined in snippet above if (midiDevices.isNotEmpty()){ midiManager.openDevice(midiDevices[0], { startWritingMidi(it, 0) }, null) } } }
Java
//AppMidiManager.java public class AppMidiManager { private native void startWritingMidi(MidiDevice device, int portNumber); private MidiManager mMidiManager; AppMidiManager(Context context){ mMidiManager = (MidiManager) context.getSystemService(Context.MIDI_SERVICE); ListmidiDevices = getMidiDevices(false); // method defined in snippet above if (midiDevices.size() > 0){ mMidiManager.openDevice(midiDevices.get(0), new MidiManager.OnDeviceOpenedListener() { @Override public void onDeviceOpened(MidiDevice device) { startWritingMidi(device, 0); } },null); } } }
AMidiDevice
çağrılarak AMidiDevice_fromJava()
oluşturulan ve ardından cihazda bir giriş bağlantı noktasını açmak için AMidiInputPort_open()
çağrısı yapılan JNI işlevi aşağıda verilmiştir:
AppMidiManager.cpp
void Java_com_nativemidiapp_AppMidiManager_startWritingMidi(
JNIEnv* env, jobject, jobject midiDeviceObj, jint portNumber) {
media_status_t status;
status = AMidiDevice_fromJava(
env, midiDeviceObj, &sNativeSendDevice);
AMidiInputPort *inputPort;
status = AMidiInputPort_open(
sNativeSendDevice, portNumber, &inputPort);
// store it in a global
sMidiInputPort = inputPort;
}
MIDI verilerini gönderme
Giden MIDI verilerinin zamanlaması iyi anlaşılıp uygulama tarafından kontrol edildiğinden veri iletimi, MIDI uygulamasının ana iş parçacığında yapılabilir. Ancak performans nedenleriyle (ör. sıralayıcıda olduğu gibi) MIDI oluşturma ve iletme işlemi ayrı bir iş parçacığında yapılabilir.
Uygulamalar, gerektiğinde MIDI verilerini gönderebilir. AMidi'nin veri yazarken engellediğini unutmayın.
Aşağıda, MIDI komutları arabelleğini alan ve yazan bir JNI yöntemi örneği verilmiştir:
void Java_com_nativemidiapp_TBMidiManager_writeMidi(
JNIEnv* env, jobject, jbyteArray data, jint numBytes) {
jbyte* bufferPtr = env->GetByteArrayElements(data, NULL);
AMidiInputPort_send(sMidiInputPort, (uint8_t*)bufferPtr, numBytes);
env->ReleaseByteArrayElements(data, bufferPtr, JNI_ABORT);
}
Aşağıdaki şemada, bir MIDI yazma uygulamasının akışı gösterilmektedir:
Geri aramalar
Yerel kodunuz, kesinlikle bir AMidi özelliği olmasa da verileri Java tarafına geri geçirmeniz (ör. kullanıcı arayüzünü güncellemek için) gerekebilir. Bunun için Java tarafında ve yerel katmanda kod yazmanız gerekir:
- Java tarafında bir geri çağırma yöntemi oluşturun.
- Geri çağırmayı çağırmak için gereken bilgileri depolayan bir JNI işlevi yazın.
Geri arama zamanı geldiğinde yerel kodunuz
Java tarafındaki geri çağırma yöntemi onNativeMessageReceive()
:
Kotlin
//MainActivity.kt private fun onNativeMessageReceive(message: ByteArray) { // Messages are received on some other thread, so switch to the UI thread // before attempting to access the UI runOnUiThread { showReceivedMessage(message) } }
Java
//MainActivity.java private void onNativeMessageReceive(final byte[] message) { // Messages are received on some other thread, so switch to the UI thread // before attempting to access the UI runOnUiThread(new Runnable() { public void run() { showReceivedMessage(message); } }); }
Aşağıda, MainActivity.onNativeMessageReceive()
için geri çağırma ayarlayan JNI işlevinin C kodu verilmiştir. Başlangıçta Java MainActivity
çağrıları
initNative()
:
MainActivity.cpp
/**
* Initializes JNI interface stuff, specifically the info needed to call back into the Java
* layer when MIDI data is received.
*/
JNICALL void Java_com_example_nativemidi_MainActivity_initNative(JNIEnv * env, jobject instance) {
env->GetJavaVM(&theJvm);
// Setup the receive data callback (into Java)
jclass clsMainActivity = env->FindClass("com/example/nativemidi/MainActivity");
dataCallbackObj = env->NewGlobalRef(instance);
midDataCallback = env->GetMethodID(clsMainActivity, "onNativeMessageReceive", "([B)V");
}
Verileri Java'ya geri gönderme zamanı geldiğinde yerel kod, geri çağırma işaretçilerini alır ve geri çağırmayı oluşturur:
AppMidiManager.cpp
// The Data Callback
extern JavaVM* theJvm; // Need this for allocating data buffer for...
extern jobject dataCallbackObj; // This is the (Java) object that implements...
extern jmethodID midDataCallback; // ...this callback routine
static void SendTheReceivedData(uint8_t* data, int numBytes) {
JNIEnv* env;
theJvm->AttachCurrentThread(&env, NULL);
if (env == NULL) {
LOGE("Error retrieving JNI Env");
}
// Allocate the Java array and fill with received data
jbyteArray ret = env->NewByteArray(numBytes);
env->SetByteArrayRegion (ret, 0, numBytes, (jbyte*)data);
// send it to the (Java) callback
env->CallVoidMethod(dataCallbackObj, midDataCallback, ret);
}
Ek kaynaklar
- AMidi referansı
- GitHub'da Native MIDI örnek uygulamasının tamamını inceleyin.