AMidi API'si Android NDK r20b ve sonraki sürümlerde kullanılabilir. Uygulama sayesinde geliştiriciler için C/C++ kodu ile MIDI verileri gönderme ve alma olanağı.
Android MIDI uygulamaları genellikle
ile iletişim kurmak için midi
API'yi
Android MIDI hizmeti. MIDI uygulamaları esas olarak
MidiManager
keşfetmek, açmak,
ve bir veya daha fazla
MidiDevice
nesne ve
cihazındaki işlemler aracılığıyla her bir cihaza veri
MIDI girişi
ve çıkış bağlantı noktalarından yararlanın:
AMidi'yi kullandığınızda MidiDevice
adresini yerel koda iletirsiniz
katmanda bulunmalıdır. Ardından AMidi bir AMidiDevice
referansı oluşturuyor
Bu, MidiDevice
ürününün çoğu işlevine sahiptir. Yerel kodunuz,
İletişim kuran AMidi işlevleri
AMidiDevice
ile doğrudan bağlantılı. AMidiDevice
doğrudan
MIDI hizmeti:
AMidi çağrılarını kullanarak uygulamanızın C/C++ ses/kontrol mantığını yakından entegre edebilirsiniz
MIDI iletimli olabilir. JNI çağrılarına veya
Uygulamanızın Java tarafı. Örneğin, C kodunda uygulanan bir dijital sentezleyici
bir JNI beklemek yerine, önemli etkinlikleri doğrudan AMidiDevice
'den almak
çağrısı ile, etkinlikleri Java tarafından aşağı
gönderin. Ya da algoritmaya dayalı besteleme,
işlemi, MIDI performansını çağrılmadan doğrudan AMidiDevice
öğesine gönderebilir
olduğu için Java tarafına
geri dönelim.
AMidi, MIDI cihazlarıyla doğrudan bağlantıyı iyileştirse de uygulamalar yine de
MidiDevice
nesneyi keşfetmek ve açmak için MidiManager
öğesini kullanın. AMidi can
buradan devam edebilirsiniz.
Bazen, kullanıcı arayüzü katmanından doğru katmana yerel koda karşılık gelir. Örneğin MIDI etkinlikleri, ekranda görebilirsiniz. Bunu yapmak için yerel mantığınıza özel JNI çağrıları oluşturun. Şu durumda: kullanıcı arayüzünü güncellemek için veri geri göndermeniz gerekiyorsa, yerel katmandan olduğu gibi.
Bu dokümanda, AMidi yerel kod uygulamasının nasıl oluşturulacağı açıklanmaktadır. MIDI komutları göndermenin ve almanın çeşitli yolları vardır. Tam bir çalışan örnek için YerelMidi örnek uygulama.
AMidi kullan
Hem amidi'yi kullanan uygulamalar hem de uygulamalar için aynı kurulum ve kapatma adımlarına sahiptir: MIDI gönderme veya alma ya da her ikisini birden yapabilirsiniz.
AMidi'yi başlat
Java tarafında, uygulamanın
bir MIDI donanımı parçası paylaşırsanız buna karşılık gelen bir MidiDevice
oluşturun.
ve yerel koda ileteceğiz.
- Java
MidiManager
sınıfıyla MIDI donanımını keşfedin. - MIDI donanımına karşılık gelen bir Java
MidiDevice
nesnesi alın. - JNI ile Java
MidiDevice
kodunu yerel koda geçirin.
Donanım ve bağlantı noktalarını keşfedin
Giriş ve çıkış bağlantı noktası nesneleri uygulamaya ait değil. Bağlantı noktalarını temsil eder
mikrofonu kullanarak. Bir cihaza MIDI verileri göndermek için uygulama,
MIDIInputPort
ve ardından verileri buna yazar. Buna karşılık, verileri almak için bir uygulama
MIDIOutputPort
açar. Düzgün çalışması için uygulamanın bağlantı noktalarının
doğru türde olduğundan emin olun. Cihaz ve bağlantı noktası keşfi, Java tarafında yapılır.
Aşağıda, her MIDI cihazını keşfeden ve bağlantı noktaları. Alma işlemi için çıkış bağlantı noktalarına sahip cihazların listesini döndürür veri veya veri göndermek için giriş bağlantı noktalarına sahip cihaz listesi içerir. MIDI cihazı, hem giriş hem de çıkış bağlantı noktaları vardır.
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
ve amidi
kitaplığına bağlantı oluşturun. Bunların ikisi de
Android NDK.
Java tarafı bir veya daha fazla MidiDevice
nesnesi ve bağlantı noktası numarasını
yerel katmanın eklenmesidir. Bu işlemin ardından yerel katman,
şu adımları uygulayın:
- Her Java
MidiDevice
içinAMidiDevice_fromJava()
kullanarak birAMidiDevice
elde edin. AMidiDevice
bölümünden birAMidiInputPort
ve/veyaAMidiOutputPort
edininAMidiInputPort_open()
ve/veyaAMidiOutputPort_open()
ile.- MIDI verilerini göndermek ve/veya almak için elde edilen bağlantı noktalarını kullanın.
AMidi'yi durdur
Java uygulaması, varsayılan özellik olmadığında yerel katmana kaynakları serbest bırakmasını bildirmelidir daha uzun süre kullanmayı konuştuk. Bunun nedeni, MIDI cihazının bağlantısı kesildiğinden veya uygulamadan çıkıldığından emin olun.
MIDI kaynaklarını serbest bırakmak için kodunuzun şu görevleri gerçekleştirmesi gerekir:
- MIDI bağlantı noktalarında okumayı ve/veya yazmaya son verin. Ölçüm özelliğini kullansaydınız girdi için yoklama yapmak üzere ileti dizisi (aşağıdaki Yoklama döngüsü uygulama bölümüne bakın), ileti dizisini durdurabilirsiniz.
- Tüm açık
AMidiInputPort
ve/veyaAMidiOutputPort
nesneyi şununla kapatın:AMidiInputPort_close()
ve/veyaAMidiOutputPort_close()
işlevleri. AMidiDevice
uygulamasınıAMidiDevice_release()
ile bırakın.
MIDI verilerini al
MIDI alan MIDI uygulamalarının tipik bir örneği, "sanal sentezleyici"dir. ses sentezini kontrol etmek için MIDI performans verilerini alan
Gelen MIDI verileri eşzamansız olarak alınır. Bu nedenle en iyisi MIDI sürekli olarak bir veya MIDI çıkış bağlantı noktasını yoklayan ayrı bir iş parçacığı içinde çalışır. Bu bir arka plan ileti dizisi veya bir sesli ileti dizisi olabilir. AMidi bağlantı noktasından okunurken engelleme yapmaz ve dolayısıyla içeride güvenle kullanılabilir. sesli geri arama.
MidiDevice ve çıkış bağlantı noktalarını ayarlayın
Bir uygulama, cihazın çıkış bağlantı noktalarından gelen MIDI verilerini okur. Java tarafı hangi cihazın ve bağlantı noktalarının kullanılacağını belirlemesi gerekir.
Bu snippet,
Android'in MIDI hizmetinden MidiManager
ve açıldı
bulduğu ilk cihaz için bir MidiDevice
. MidiDevice
açılan bir geri çağırma alındıysa
MidiManager.OnDeviceOpenedListener()
. Bunun onDeviceOpened
yöntemi
işleyici çağrılır ve ardından 0 numaralı çıkış bağlantı noktasını açmak için startReadingMidi()
çağrısı yapılır
cihaz üzerinde. Bu
AppMidiManager.cpp
içinde tanımlanan bir JNI işlevidir. Bu işlev
nasıl yapılacağını göstereceğim.
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ı MIDI cihazını ve bağlantı noktalarını AMidi işlevleri tarafından kullanılan referanslar.
Aşağıdaki gibi çağırarak AMidiDevice
oluşturan JNI işlevi verilmiştir:
AMidiDevice_fromJava()
ve ardından açmak için AMidiOutputPort_open()
numaralı telefonu arar
cihazdaki bir çıkış bağlantı noktası:
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,
AMidiOutputPort_receive()
, sıfırdan büyük bir sayı döndürür.
MIDI kapsamı gibi düşük bant genişliğine sahip uygulamalarda düşük öncelikli bir anket yapabilirsiniz. (uygun uykularla) arka plan ileti dizisi.
Ses üreten ve daha sıkı gerçek zamanlı performansa sahip uygulamalar için
gerekiyorsa, ana ses oluşturma geri aramasında (
BufferQueue
için geri çağırma (OpenSL ES, AAudio'da AudioStream veri geri çağırması) yapar.
AMidiOutputPort_receive()
engelleme olmadığından, çok az
nasıl etkilediğini öğreneceğiz.
startReadingMidi()
işlevinden çağrılan readThreadRoutine()
fonksiyonu
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 ses API'si (ör. OpenSL ES veya AAudio) aşağıdaki gibi ses oluşturma geri çağırmasına 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, bir MIDI okuma uygulamasının akışı gösterilmektedir:
MIDI verilerini gönder
MIDI yazma uygulamalarına tipik bir örnek olarak MIDI denetleyicisi veya sıralayıcısı verilebilir.
MidiDevice ve giriş bağlantı noktalarını ayarlayın
Bir uygulama, giden MIDI verilerini MIDI cihazının giriş bağlantı noktalarına yazıyor. Java tarafı hangi MIDI cihazının ve bağlantı noktalarının kullanılacağını belirlemesi gerekir.
Aşağıdaki kurulum kodu, yukarıdaki alma örneğindeki bir varyasyondur. MidiManager
oluşturur
Google'ın MIDI hizmetinden
yararlanabilir. Ardından bulduğu ilkMidiDevice
öğeyi açar ve
cihazdaki ilk giriş bağlantı noktasını açmak için startWritingMidi()
işlevini çağırır. Bu,
JNI çağrısı AppMidiManager.cpp
içinde tanımlandı. Fonksiyon
sonraki snippet'i seçin.
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); } } }
Aşağıdaki gibi çağırarak AMidiDevice
oluşturan JNI işlevi verilmiştir:
AMidiDevice_fromJava()
ve ardından açmak için AMidiInputPort_open()
numaralı telefonu arar
cihazdaki bir giriş bağlantı noktası:
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önder
Giden MIDI verilerinin zamanlaması Google tarafından iyi anlaşıldığından ve kontrol edildiğinden uygulamanın kendisinde veri iletimi, MIDI uygulamasının ana iş parçacığında yapılabilir. Ancak, performans nedeniyle (bir sıralayıcıda olduğu gibi) oluşturma ve MIDI iletimi ayrı bir iş parçacığında yapılabilir.
Uygulamalar gerektiğinde MIDI verileri gönderebilir. AMidi'nin aşağıdaki durumlarda engellediğini unutmayın: verileri yazma konusunda konuşacağız.
Burada, MIDI komutların arabelleğini alan ve şöyle yazıyor:
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, MIDI yazma uygulamasının akışı gösterilmektedir:
Geri çağırma işlevleri
Tam olarak bir AMidi özelliği olmasa da yerel kodunuzun verileri iletmesi gerekebilir Java tarafına geri dönün (örneğin, kullanıcı arayüzünü güncellemek için). Bunu yapmak için Java tarafında ve yerel katmana kod yazın:
- 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 çağırma zamanı geldiğinde, yerel kodunuz
Java tarafı geri çağırma yöntemi olan onNativeMessageReceive()
şu şekildedir:
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()
Java MainActivity
çağrıları
Başlangıçta 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");
}
Java'ya geri veri gönderme zamanı geldiğinde yerel kod, geri çağırmayı alır gösterir 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ı
- Yerel MIDI örnek uygulamasının tamamını github'da bulabilirsiniz.