Android MIDI sequencer with Processing interface

Cool. Just tried it with my USB UNO MIDI interface connected to my Korg M1 and it does recognize it.

2 Likes

hm, I’m getting an error when trying to send a note-on.

inputPort.send(buffer, offset, numBytes);
Unhandled execption type IOExecption.

So, again, waiting for shedmusics solution.

1 Like

@chanof @sensn

Great to hear it’s starting to work!! Sorry it’s all a bit slow and in bits, but it takes time to mess about, and I have real work to do as well as this fun stuff!

More soon :slight_smile:

1 Like

@shedMusic Don’t worry :smiley: your work here it’s amazing i agree with @sensn could become one really important feature in MIDI stuff.

I have a question for @sensn it is not clear for me how you can read the result of the console if obviously the phone is connected to the computer, I think you have exported the resulted strings to display them on the phone screen, like below, right now I only have a midi usb keyboard, i trying to connect it to the phone but i got always the same result from the midi scan:
Which say:
manufacturer: AndroidTest
Product: SynthExample

I put the code in the void draw to get a midi scan in real time but I always get a static result.

Could i ask if is my approach correct?
Thanks

import android.app.Activity;
import android.content.Context;
import android.os.Bundle ;
import android.media.midi.* ;
import android.content.pm.PackageManager ;
import android.os.Handler ;
import android.os.Looper ;

PFont font;

Activity act;
Context context;
MidiManager m ;

String check;
String noDevices;
String numDevices;
String nDevices;
String numInPorts;
String numOutPorts;
String manufacturer;
String product;
String name;
String serialNumber;
String ports;
String midiInfo;
String ready;


void setup() {
  

  font = createFont("Lucida Console", 32);
  textFont(font);
//  

}

void draw() {
  
  
  
  act = this.getActivity();
  context = act.getApplicationContext();
  m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
  //check device supports MIDI
  if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
    // Phone.tablet supports MIDI
    check = "great .... can do midi things";
    //search for attached devices
    MidiDeviceInfo[] deviceInfo = m.getDevices();
    if (deviceInfo.length == 0) {
      noDevices = "No MIDI devices found  .. " + deviceInfo.length;
    } else {
      noDevices = "Num MIDI  Devices found  = " + deviceInfo.length;
      Bundle properties ;
      for (int i=0; i<deviceInfo.length; i++) {
        numDevices = "Device " + i;
        numInPorts = "  Num MIDI inputs = " + deviceInfo[i].getInputPortCount();
        numOutPorts = "  Num MIDI outputs = " + deviceInfo[i].getOutputPortCount();
        properties = deviceInfo[i].getProperties();
        manufacturer = "  Manufacturer = " + properties.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER); 
        product = "  Product =" + properties.getString(MidiDeviceInfo.PROPERTY_PRODUCT);
        name = "  Name = " + properties.getString(MidiDeviceInfo.PROPERTY_NAME);
        serialNumber = "  Serial Number = " + properties.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER);
        //Port Information
        MidiDeviceInfo.PortInfo[] portInfo = deviceInfo[i].getPorts();
        ports = "    Num ports found = " + portInfo.length;
        for (int j=0; j<portInfo.length; j++) {
          midiInfo = "    port " + j + " Name = " + portInfo[j].getName() + "Type = " + portInfo[j].getType();
        }// end j loop
        println(""); // to give a blank space in the console
      }//end i loop
      
           //Try to open a device!
      //To get this bit to work, make sure you have imported  and android.os.Handler and android.os.Looper at the top of your sketch;
      //Still experimental at this stage but is working
      //I have used deviceInfo[0] here which for me (at least for now) is FluidSynth
      m.openDevice(deviceInfo[0], new MidiManager.OnDeviceOpenedListener() {
        @Override
          public void onDeviceOpened(MidiDevice device) {
          if (device == null) {
            ready = "could not open device";
          } else {
            ready = "device opened and ready for use";
          }
        }
      }
      , new Handler(Looper.getMainLooper())
        );
      //and that ends opening a device ... phew...!
      //next ... playing some notes ... I hope
    }
  } else {
    check = "boo .... no midi possible";
  }
   
  text(check, 10, 30); 
  text(noDevices, 10, 70); 
  text(numDevices, 10, 110); 
  text(numInPorts, 10, 150); 
  text(numOutPorts, 10, 190); 
  text(manufacturer, 10, 230); 
  text(product, 10, 270); 
  text(name, 10, 310);
  text(serialNumber, 10, 350);
  text(ports, 10, 390);
  text(midiInfo, 10, 430);
  text(ready, 10, 470);
  
}
1 Like

@chanof

I used a stringList and a string for that. But took me a while too, to get the desired result.

My verry messy way was:
Before setup add

StringList sl;
String s;

in setup() add

sl = new StringList();

after the line

println(" Serial Number = " + properties.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER));

add:

s = properties.getString(MidiDeviceInfo.PROPERTY_PRODUCT);
sl.append(s);

in draw() add:

String item = sl.get(0);  // change index here
text(item, width/2, height/2);

Unplugged i have 2 Midi devices, when i plug my Midi interface I’ve got 3, so when i give it index 2 it prints the correct message.

Be prepared that the app will quit the first time you try it with the index of the unplugged device. then plug it in and rerun the app. That worked for me.

It iterates through the for…next loop, that’s why I used stringList.

I doubt this is the best way to do it but it shows the name of the Midi-interface.

2 Likes

@sensn thanks for support and for patient :slight_smile: but i get every time that the app quit its self. Only 0 index respond with “SynthExample”
Where do you put Mark’s code: inside setup or inside draw?
Are you changing the index in real time on the phone interface or with static value before running it?
Is this the correct code with your corrections to get the external device:

import android.app.Activity;
import android.content.Context;
import android.os.Bundle ;
import android.media.midi.* ;
import android.content.pm.PackageManager ;
import android.os.Handler ;
import android.os.Looper ;

Activity act;
Context context;
MidiManager m ;

//SENSN
StringList sl;
String s;
//

void setup() {
  sl = new StringList();
  act = this.getActivity();
  context = act.getApplicationContext();
  m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
  //check device supports MIDI
  if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
    // Phone.tablet supports MIDI
    println("great .... can do midi things");
    //search for attached devices
    MidiDeviceInfo[] deviceInfo = m.getDevices();
    if (deviceInfo.length == 0) {
      println("No MIDI devices found  .. " + deviceInfo.length);
    } else {
      println("Num MIDI  Devices found  = " + deviceInfo.length);
      Bundle properties ;
      for (int i=0; i<deviceInfo.length; i++) {
        println("Device " + i);
        println("  Num MIDI inputs = " + deviceInfo[i].getInputPortCount());
        println("  Num MIDI outputs = " + deviceInfo[i].getOutputPortCount());
        properties = deviceInfo[i].getProperties();
        println("  Manufacturer = " + properties.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER)); 
        println("  Product =" + properties.getString(MidiDeviceInfo.PROPERTY_PRODUCT));
        println("  Name = " + properties.getString(MidiDeviceInfo.PROPERTY_NAME));
        println("  Serial Number = " + properties.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER));
        
        //SENSN
        s = properties.getString(MidiDeviceInfo.PROPERTY_PRODUCT);
        sl.append(s);
        //
        
        //Port Information
        MidiDeviceInfo.PortInfo[] portInfo = deviceInfo[i].getPorts();
        println("    Num ports found = " + portInfo.length);
        for (int j=0; j<portInfo.length; j++) {
          println("    port " + j + " Name = " + portInfo[j].getName() + "Type = " + portInfo[j].getType());
        }// end j loop
        println(""); // to give a blank space in the console
      }//end i loop
      m.openDevice(deviceInfo[0], new MidiManager.OnDeviceOpenedListener() {
        @Override
          public void onDeviceOpened(MidiDevice device) {
          if (device == null) {
            println("could not open device");
          } else {
            println("device opened and ready for use");
          }
        }
      }
      , new Handler(Looper.getMainLooper())
        );
    }
  } else {
    println("boo .... no midi possible");
  }
}

void draw() {  
String item = sl.get(1); // change index here (only 0 work)
text(item, width/2, height/2);
}
2 Likes

Yes, that should work. How many Midi devices are detected when you have the phone connected to computer?
Do you have other Midi apps on your phone to see if the interface is working?
I have an app called Midicommander, try that to see if your Interface is detected.

Hm, it seems like the order of Midi devices are changed when plugging in.
I tried again and got troubles too.
This works for me now. Only draw() changed. If this doesn’t work try commenting out the last two lines

void draw() {  
textSize(32);
  String item = sl.get(0); 
  text(item, width/2, height/2);
  String item1 = sl.get(1); 
  text(item1, width/3, height/3);
   String item2 = sl.get(2); 
  text(item2, width/4, height/4);
}

It’s a lot of trail and error, and I’m not a real coder…

2 Likes

If it works add ‘i’ to the append line

sl.append(i+s);

The order of MIDI devices always changes when plugging in. We will need to know which number our device is so we can open it.

My phone now shows: 0 USB Uno MIDI interface

1 Like

Whoops sorry…

It shows 0 USB Uno MIDI interface
1 Midi File Player
2 Fluid Synth
But the order changes when re-plugging.

1 Like

Thanks a lot @sensn, a lot try and error i agree, this mission it’s so far away from my code skills but i hope to be useful as beta tester.
Im gonna start another test session :wink:

1 Like

I got errors with the last two elements, only with the first one work, which is something by default of android “SynthExample”
At the moment i don’t have any other midi app on the phone but i presume that if SynthExample work also other stuff inside the phone could work.
Tomorrow i will ask an analog gear to a friend for tests, but the the midi-usb piano i have should be correctly be scanned, i don’t think that it is a big problem but i can’t figure out where the issue is in this moment, i also have to buy a midi adapter like your.

But i don’t understand why you don’t see SynthExample as 0 in the list

my phone is samsung S7 edge
android 8.0.0
I should try whit other phones

Tomorrow i will restart my tests.
Happy to work with you guys!

1 Like

A few things moved around …variables declared with global scope as required outside setup()
setup() just sets set up the basics
mousePressed() calls sendMidi()
sendMidi() sends 2 notes to the device which get played by FluidSynth

I think this starts to be useful :slight_smile:


import android.app.Activity;
import android.content.Context;
import android.os.Bundle ;
import android.os.Handler ;
import android.os.Looper ;
import android.media.midi.* ;
import android.content.pm.PackageManager ;

Activity act;
Context context;
MidiManager m ;
MidiDeviceInfo[] deviceInfo ;
MidiDeviceInfo.PortInfo[] portInfo ;
MidiInputPort inputPort ;
Bundle properties ;

void setup() {
  act = this.getActivity();
  context = act.getApplicationContext();
  m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
  //check device supports MIDI
  if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
    // Phone.tablet supports MIDI
    println("great .... can do midi things");
    //search for attached devices
    deviceInfo = m.getDevices();
    if (deviceInfo.length == 0) {
      println("No MIDI devices found  .. " + deviceInfo.length);
    } else {
      println("Num MIDI  Devices found  = " + deviceInfo.length);

      for (int i=0; i<deviceInfo.length; i++) {
        println("Device " + i);
        println("  Num MIDI inputs = " + deviceInfo[i].getInputPortCount());
        println("  Num MIDI outputs = " + deviceInfo[i].getOutputPortCount());
        properties = deviceInfo[i].getProperties();
        println("  Manufacturer = " + properties.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER)); 
        println("  Product =" + properties.getString(MidiDeviceInfo.PROPERTY_PRODUCT));
        println("  Name = " + properties.getString(MidiDeviceInfo.PROPERTY_NAME));
        println("  Serial Number = " + properties.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER));
        //Port Information
        portInfo = deviceInfo[i].getPorts();
        println("    Num ports found = " + portInfo.length);
        for (int j=0; j<portInfo.length; j++) {
          println("    port " + j + " Name = " + portInfo[j].getName() + "Type = " + portInfo[j].getType());
        }// end j loop
        println(""); // to give a blank space in the console
      }//end i loop
      //Try to open a device!
      //To get this bit to work, make sure you have imported android.os.Bundle and android.os.Handler  at the top of your sketch;
      //Still experimental at this stage but is working
      //I have used deviceInfo[0] here which for me (at least for now) is FluidSynth
      m.openDevice(deviceInfo[0], new MidiManager.OnDeviceOpenedListener() {
        @Override
          public void onDeviceOpened(MidiDevice device) {
          if (device == null) {
            println("could not open device");
          } else {
            println("device opened and ready for use");
            //Open input port
            inputPort = device.openInputPort(0);
          }//end else
        }
      }
      , new Handler(Looper.getMainLooper())
        );
    } // end do things with MIDI
  } else {
    println("boo .... no midi possible");
  }
}

void draw() {
}

void mousePressed() {
  sendMidi();
}


void sendMidi() {
  //Send MIDI note data
  byte[] buffer = new byte[32];
  int numBytes = 0;
  int channel = 3; // MIDI channels 1-16 are encoded as 0-15.
  buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
  buffer[numBytes++] = (byte)60; // pitch is middle C
  buffer[numBytes++] = (byte)127; // max velocity
  int offset = 0;
  // post is non-blocking 
  try {
    inputPort.send(buffer, offset, numBytes);
    println("data sent");
  }
  catch (Exception e) {
    println("error sending midi data");
  }
  buffer = new byte[32];
  numBytes = 0;
  channel = 3; // MIDI channels 1-16 are encoded as 0-15.
  buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
  buffer[numBytes++] = (byte)65; // pitch is middle C
  buffer[numBytes++] = (byte)127; // max velocity
  offset = 0;
  // post is non-blocking 
  try {
    inputPort.send(buffer, offset, numBytes);
    println("data sent");
  }
  catch (Exception e) {
    println("error sending midi data");
  }
}
2 Likes

ok i can play fluid from your sketch, this is a good start, unfortunately apogee duet sound card have an usb female as midi in/out which give me already problems with other stuff.
Actually your code find two midi devices, i scope them with the method exposed by @sensn
FluidSynth 1.1.6 and SynthExample are correctly scanned.
Im pretty sure that with the cable of sens it will see another out.

1 Like

This works with external hardware too. Just tried it and it works. This is so cool. Thanks a lot!

@sensn that’s good to hear!

Next will be working out how to have a reliable clock for timing the sending of notes!

2 Likes

Here is a little s 16 step sequencer i quickly put together. It plays a Min+7/9 chord. Maybe it’s useful for testing.

import android.app.Activity;
import android.content.Context;
import android.os.Bundle ;
import android.os.Handler ;
import android.os.Looper ;
import android.media.midi.* ;
import android.content.pm.PackageManager ;

Activity act;
Context context;
MidiManager m ;
MidiDeviceInfo[] deviceInfo ;
MidiDeviceInfo.PortInfo[] portInfo ;
MidiInputPort inputPort ;
Bundle properties ;

int savedTime;
int totalTime = 250;

// An array of buttons
Button[] buttons = new Button[80];

int mc = 0;
int rx = -1;
int mx= 0;

void setup() {
  size(1200, 400);
  savedTime = millis();
  // A loop to evenly space out the buttons along the window
  for (int j = 0; j < 5; j++) {
  for (int i = 0; i < 16; i++) {
    
    buttons[mc] = new Button(i*55+25, j*55+25, 50, 50);
    mc += 1;
}
}


act = this.getActivity();
  context = act.getApplicationContext();
  m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
  //check device supports MIDI
  if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
    // Phone.tablet supports MIDI
    println("great .... can do midi things");
    //search for attached devices
    deviceInfo = m.getDevices();
    if (deviceInfo.length == 0) {
      println("No MIDI devices found  .. " + deviceInfo.length);
    } else {
      println("Num MIDI  Devices found  = " + deviceInfo.length);

      for (int i=0; i<deviceInfo.length; i++) {
        println("Device " + i);
        println("  Num MIDI inputs = " + deviceInfo[i].getInputPortCount());
        println("  Num MIDI outputs = " + deviceInfo[i].getOutputPortCount());
        properties = deviceInfo[i].getProperties();
        println("  Manufacturer = " + properties.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER)); 
        println("  Product =" + properties.getString(MidiDeviceInfo.PROPERTY_PRODUCT));
        println("  Name = " + properties.getString(MidiDeviceInfo.PROPERTY_NAME));
        println("  Serial Number = " + properties.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER));
        //Port Information
        portInfo = deviceInfo[i].getPorts();
        println("    Num ports found = " + portInfo.length);
        for (int j=0; j<portInfo.length; j++) {
          println("    port " + j + " Name = " + portInfo[j].getName() + "Type = " + portInfo[j].getType());
        }// end j loop
        println(""); // to give a blank space in the console
      }//end i loop
      //Try to open a device!
      //To get this bit to work, make sure you have imported android.os.Bundle and android.os.Handler  at the top of your sketch;
      //Still experimental at this stage but is working
      //I have used deviceInfo[0] here which for me (at least for now) is FluidSynth
      m.openDevice(deviceInfo[0], new MidiManager.OnDeviceOpenedListener() {
        @Override
          public void onDeviceOpened(MidiDevice device) {
          if (device == null) {
            println("could not open device");
          } else {
            println("device opened and ready for use");
            //Open input port
            inputPort = device.openInputPort(0);
          }//end else
        }
      }
      , new Handler(Looper.getMainLooper())
        );
    } // end do things with MIDI
  } else {
    println("boo .... no midi possible");
  }
}



void draw() {
  background(255);
  // Show all the buttons
  for (int i = 0; i < buttons.length; i++) {
    buttons[i].display();
  }

// Calculate how much time has passed
  int passedTime = millis() - savedTime;
  // Has time passed?
  if (passedTime > totalTime) {
    
    
 
  rx += 1;
    mx= rx % 16;
     if (buttons[mx].on == true){
  println ("EVENT A" +mx);
sendMidi(33);}  
  if (buttons[mx+16].on == true){
  println ("EVENT B" +mx);
sendMidi(36);} 
  if (buttons[mx+32].on == true){
  println ("EVENT C" +mx);
sendMidi(40);} 
  if (buttons[mx+48].on == true){
  println ("EVENT D" +mx);
sendMidi(43);} 
  if (buttons[mx+64].on == true){
  println ("EVENT E" +mx);
sendMidi(47);}
    savedTime = millis();// Save the current time to restart the timer!
} 

    
    fill(255,0,0);
    rect(mx*55+25,0,50,20);
    
}

void mousePressed() {
  // When the mouse is pressed, we must check every single button
  for (int i = 0; i < buttons.length; i++) {
    buttons[i].click(mouseX, mouseY);
  }
}


class Button  {    

  // Button location and size
  float x;   
  float y;   
  float w;   
  float h;   
  // Is the button on or off?
  boolean on;  

  // Constructor initializes all variables
  Button(float tempX, float tempY, float tempW, float tempH)  {    
    x  = tempX;   
    y  = tempY;   
    w  = tempW;   
    h  = tempH;   
    on = false;  // Button always starts as off
  }    

  void click(int mx, int my) {
    // Check to see if a point is inside the rectangle
    if (mx > x && mx < x + w && my > y && my < y + h) {
      on = !on;
    }
  }

  // Draw the rectangle
  void display() {
    rectMode(CORNER);
    stroke(0);
    // The color changes based on the state of the button
    if (on) {
      fill(175);
    } else {
      fill(0);
    
    }
    rect(x,y,w,h);
  }
  
} 
void sendMidi(int thenote) {
  //Send MIDI note data
  byte[] buffer = new byte[32];
  int numBytes = 0;
  int channel = 1; // MIDI channels 1-16 are encoded as 0-15.
  buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
  buffer[numBytes++] = (byte)thenote; // pitch is middle C
  buffer[numBytes++] = (byte)127; // max velocity
  int offset = 0;
  // post is non-blocking 
  try {
    inputPort.send(buffer, offset, numBytes);
    println("data sent");
  }
  catch (Exception e) {
    println("error sending midi data");
  }
  buffer = new byte[32];
  numBytes = 0;
  channel = 3; // MIDI channels 1-16 are encoded as 0-15.
  buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
  buffer[numBytes++] = (byte)(thenote + 12); // pitch is middle C
  buffer[numBytes++] = (byte)127; // max velocity
  offset = 0;
  // post is non-blocking 
  try {
    inputPort.send(buffer, offset, numBytes);
    println("data sent");
  }
  catch (Exception e) {
    println("error sending midi data");
  }
}
3 Likes

Me too i got it working for external gears, just with a midi interface from usb to midi in/out.
Clock and timestamp are great things. Thanks a lot @shedMusic

1 Like

@shedMusic @chanof

I think I found a bug. If I add ‘orientation(LANDSCAPE);’ to my setup() it seems like it breaks the MIDI. No Midi output there. Moreover if i rotate the phone then there is no midi too. It seems like the sketch is restarted when rotating the phone, that’s why i tried ‘orientation(LANDSCAPE);’ but this doesn’t work either. I tried several times, this happens on Samsung Note 4.
That’s a pitty. Sequencers would be nice in Landscape-view.
If anyone got some time, would you mind trying it and telling me if it works at your devices?
thanks.

1 Like

Thanks @sensn it’s the whole afternoon i search why my sequencer can’t send midi and yours can, now i understand

2 Likes

@chanof
Ok, meanwhile we can code everything rotated by 90 degrees. might be a nice exercise.

1 Like