Android MIDI sequencer with Processing interface


#63

Great, I’ll test. If i didn’t touch the display for some time, it turned off, and I had to restart the app too. Maybe that issue is gone now too.

For timing I tried gotoloops Countdown Class, it works pretty good.
https://forum.processing.org/two/discussion/27733/countdown-class-library-for-java-js-python
I implemented a tempo slider, but i didn’t sync it to any clock, so i don’t know how accurate it really is. I’m curious what shedMusic will come up with.
I also worked on a M1 front panel sketch, which works verry well. It’s great to have that MIDI feature.


#64

@shedMusic

It works. But I think it only works as long as you don’t have

orientation(LANDSCAPE); 

in your setup(). Tried several times now and whenever I’ve got it in setup() Midi was locked.
Now i call orientation(LANDSCAPE); from draw() and that works.


#65

what’s about CountdownTimer lib?


#66

@sensn Is that using FluidSynth or an external device or both?


#67

Hi Mark
-I declared the new variable
-I moved the whole midi setup code again inside setup () and modified the part with the new variable
-I copied the void onPause

But it don’t work, im sorry, i think to don’t know where to insert the myOpenDevice, may i ask an help?


#68

@chanof

just on these 2 lines:

        myOpenDevice = device;
        inputPort = myOpenDevice.openInputPort(0);

#69

@shedMusic
I always test with my external synth.

I think it’s better not use orientation(LANDSCAPE);

When i call it from draw() it works, but not for the screensaver pause thing.
Without orientation(LANDSCAPE) ; both issues are gone; but when the phone goes idle, the sketch restarts in Portrait mode, so i again have to rotate it. I’d prefer to leave it on the top of my synth.


#70

@sensn

ok … more thinking needed!


#71

Do i leave onPause() as you write it? where to recall it? Thanks


#72

@chanof

the code works as shedMusic has posted it. are you sure you have completely removed my workaround and orientation(LANDSCAPE) ?


#73

@sensn Why remove orientation(LANDSCAPE) ? Your fix to put the code into a specific void and then recall it one time into the draw, allows us to continue working with the landscapes view, im sorry have i missing something?


#74

@chanof
yes, the fix works just as long as you don’t use orientation(LANDSCAPE) in your setup.

shedMusic suggests not to use my workaround, and I propose to follow his suggestion…
I think he will come up with a complete fix until that you can work with my workaround or use the fix without orientation(LANDSCAPE);


#75

@sensn

One more test please… can you see what happens

  1. if you start the sketch with the phone already held in landscape
  2. if you start the sketch with the phone in held in portrait

#76

@shedMusic

Without orientation(LANDSCAPE); it starts in the expected mode. Portrait when held in portrait. Landscape when held in Landscape.
The only thing is if i start in Landscape and i put it on my synth, and the screensaver starts, when i turn it on again the sketch restarts in Portrait.

With orientation(LANDSCAPE); it’s always in Landscape but will break Midi only after screensaver. (when called from draw().

with orientation in setup(), Midi is locked right at the start. and it doesn’t matter if it’s before or after your Midi code.
I think that might be because orientation(LANDSCAPE); restarts the setup() before onPause() can be called, but I don’t know.


#78

@shedMusic

oh, sorry again. Now I was in the first line of draw(). my previous messages still seem to be true.


#79

@sensn @chanof

OK, more testing done …

edit your manifest file in the sketch folder to set landscape

<activity android:name=".MainActivity" android:screenOrientation=“landscape” … etc …

and don’t do anything in you code.

This seems to stop any attempt to set portrait.


#80

yes, now it works perfectly, and the flickering when starting the app is gone too. Great.
I guess sometimes we have to think outside the box.:slightly_smiling_face:


#81

Great
And now I can get on with the timing stuff. :grinning:
I think the countdown timer from gotoloop uses milliseconds and this is likely to cause issues for midi, where standard resolution is nano. I am fiddling with it today to see how it might work for a sequencer.


#82

@sensn @chanof

Incomplete and for testing … and for you to see if you can adapt to your sketches. I have for future reasons created a class called ShedMidi that wrap up all the things I last did in setup, so now I create an instance of that … still does exactly the same though as before.

Timing …
I have an array that just contains 16 long values that represent the start times for each grid position, relative to t=0. I then have a function that iterates through this array and calls sendMidi() when enough time has elapsed. This runs as a thread called from MousePressed(), so it can’t be interred with by any processing going on in draw() … should be better timing and less jitter.

See how it goes …


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

Activity act;
Context context;
Bundle properties ;
ShedMidi shedMidi ;

// just for testing ... an array to simulate note start times .. starting at t=0 for first note
long[] noteStartTimes = new long[16];
//length in nanoseconds of a single cell in the grid
long cellLength = (long)(0.125*1000000000L) ; // 1/16th note at 120bpm ... use a bpm slider to update this (using mouseDragged() maybe?)

boolean isPlaying = false ;


void setup() {
  fullScreen(P2D);
  act = this.getActivity();
  context = act.getApplicationContext();
  println("setup m");
  shedMidi = new ShedMidi() ;
  for (int i=0; i<16; i++) {
    noteStartTimes[i] = i*cellLength ;
  }
}

void draw() {
  background(0);
  fill(255);
  textSize(40);
  textAlign(CENTER, CENTER);
  text("LANDSCAPE", 0, 0, width, height);
}

void mousePressed() {
  //would be better to have start & stop buttons , but for testing  ....
  if (!isPlaying) {
    isPlaying = true ;
    thread("playSequence");
  } else {
    isPlaying = false ;
  }
}


//call as a thread e.g. as in mousePressed()
void playSequence() {
  long startTime = System.nanoTime();
  int index = 0 ;
  while (index < 16 && isPlaying) {
    if (noteStartTimes[index] < System.nanoTime() - startTime) {   
      shedMidi.sendMidi();      
      index++ ;
    }
  }
}

class ShedMidi {
  MidiManager m ;
  MidiDevice myOpenDevice ;
  MidiDeviceInfo[] deviceInfo ;
  MidiDeviceInfo.PortInfo[] portInfo ;
  MidiInputPort inputPort ;
  ShedMidi() {
    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
      println("setup deviceInfo");
      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
        m.openDevice(deviceInfo[0], new MidiManager.OnDeviceOpenedListener() {
          @Override
            public void onDeviceOpened(MidiDevice device) {
            if (device == null) {
              println("could not open device");
            } else {
              myOpenDevice = device;
              println("device opened and ready for use");
              //Open input port
              inputPort = myOpenDevice.openInputPort(0);
            }//end else
          }
        } //end m.opendevice
        , new Handler(Looper.getMainLooper())
          );
      } // end do things with MIDI
    } else {
      println("boo .... no midi possible");
    }
  }//end constructor
  //methods
  
   
  void sendMidi() {
    //Send MIDI note data ... for info .send() is a methid from MidiReceiver class which 
    byte[] buffer = new byte[32];
    int numBytes = 0;
    int channel = 10; // MIDI channels 1-16 are encoded as 0-15.
    buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
    buffer[numBytes++] = (byte)42; // closed hi hat
    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");
    }
  }
}// end ShedMidi class





void onPause() {
  try {
    shedMidi.inputPort.close();
  } 
  catch(Exception e) {
  }
}


#83

@shedMusic
Very Great! I can hear that this timing is a lot more exact. Great work.