Yerel MIDI API'sı

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.

  1. Java MidiManager sınıfıyla MIDI donanımını keşfedin.
  2. MIDI donanımına karşılık gelen bir Java MidiDevice nesnesi alın.
  3. 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 List getMidiDevices(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:

  1. Her Java MidiDevice için AMidiDevice_fromJava() kullanarak bir AMidiDevice elde edin.
  2. AMidiDevice bölümünden bir AMidiInputPort ve/veya AMidiOutputPort edinin AMidiInputPort_open() ve/veya AMidiOutputPort_open() ile.
  3. 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:

  1. 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.
  2. Tüm açık AMidiInputPort ve/veya AMidiOutputPort nesneyi şununla kapatın: AMidiInputPort_close() ve/veya AMidiOutputPort_close() işlevleri.
  3. 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);
    List midiDevices = 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), &timestamp);
        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), &timestamp);
    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);
    List midiDevices = 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