Android MIDI sequencer with Processing interface


#84

It was pretty simple in the end for a quick demo, although we’ll see what happens with multiple tracks and channels!


#85

Ok, I have adapted it to my code now… , but need to connect the tempo slider next. More testing soon.


#86

@shedMusic
I’m sorry if this is a silly question, but how would i create a loop so that the sequence is started again once finished.
I can’t do it. The thread playsequence shall be only called from mousePressed and not from draw,right? I tried some things but got strange results.
If you could give an example that would be very nice. Thanks.
EDIT:
OK, got the loop, still struggling with tempo slider :sweat_smile: It’s very exiting.

EDIT2: Got temposlider working too. This timer is really way better. Cool.


#87

You beat me to it…

new global …

boolean loopPlay = true ;

and

//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++ ;
    }
    if (index == 15 && loopPlay) { //reset things
      startTime = System.nanoTime() + cellLength;
      index = 0 ;
    }
  }//end while
}//END

sounds like you are getting on well!


#88

This looks promising. I have some troubles with resetting, I was resetting things in the if statement, then i got the last note and first note playing at the same time.
This is almost what I tried.
Thanks a lot for your time and your help.


#89

Yes I did that too at first … !
No worries re helping … I have been wanting to tackle midi for ages … so this is a real motivator for me.
Wish I had more time to give to it!


#90

I think for 16 Steps it needs to be

 if (index == 16 && loopPlay)

That’s what works for me.
If you are interested I can share what I have till now, still struggling with some buttons… but it’s fun.


#91

Yes you’re right , it should be 16.

Yes I’d be interested to see what you have done. Email me a zip again if it’s too big to post here easily !

I’m seeing how I can add midi to my existing app too now. Have been using samples and audio up to now but using midi and having FluidSynth and external gear is a great next step!


#92

Thanks for the massive work @shedMusic I can’t control the trigger of the clock by a toggle rather than void mouse pressed.

I have a “mouse pressed” inside every toggle,

How can I check the clock by reading the boolean variable inside the toggle?

I’d like to know if you think it’s better to write mouse actions in a “void mouse pressed”?

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 ;
boolean loopPlay = true ;
int block;

Toggle[] playStop = new Toggle[2];



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 ;
  }
  block = width/21;
  //declare play button
  for (int x = 0; x < 2; x++) {
    playStop[x] = new Toggle((block/2)+(2*block)* (8 + x), (block/2) + (block*8), block*2, block*2);
  }
  thread("playSequence");
}

void draw() {
  background(0);
  fill(255);
  textSize(40);
  textAlign(CENTER, CENTER);
  text("LANDSCAPE", 0, 0, width, height);
  for (int y = 0; y < 2; y++) {
    playStop[y].draw();
  }
}


//call as a thread e.g. as in mousePressed()
void playSequence() {
  long startTime = System.nanoTime();
  int index = 0 ;
  while (index < 16 && playStop[0].state) {
    if (noteStartTimes[index] < System.nanoTime() - startTime) {   
      shedMidi.sendMidi();      
      index++ ;
    }
    if (index == 15 && loopPlay) { //reset things
      startTime = System.nanoTime() + cellLength;
      index = 0 ;
    }
  }//end while
}//END

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) {
  }
}



//Toggles haves three states:
//state: toggle is on or off
//blink: toggle blinking, it' time to play
//ready: it is ready to change state from off to on

class Toggle {
  boolean state;
  boolean ready;
  boolean blink;
  boolean mode;
  boolean accent;
  int x, y, w, h, delta;
  Toggle(int ix, int iy, int iw, int ih) {
    blink = false;
    state = false;
    ready = true;
    mode = false;
    accent = false;
    x = ix;
    y = iy;
    w = iw;
    h = ih;
  }
  void draw() {
    if(blink == true){delta = 32;} else {delta = 0;}
    
    if ( mousePressed ) {
      if (ready && mouseX >= x && mouseX <= (x+w) && mouseY >= y && mouseY <= (y+h) ) {
        ready = false;
        if(!mode){
        state = !state;
        }else 
        {
        accent = !accent;
        }
      }
    } else {
      ready = true;
    }
    
    fill(63+delta);
    
    if (state) {
      fill(127+delta);
    }
    
    if(!accent){
    rect(x, y, w, h);
    }else{ rect(x+(block/4), y+(block/4), w/2, h/2);  }
  }
}

#93

@chanof
trying your code now

EDIT:

You should definitely move all mouse handing away from draw() … by using if(mousePressed) inside draw you are running that code on every frame at the framerate which a means you are running it far more times than than necessary (once is enough to change the state), and b) as you do more in draw() to develop your GUI the framerate will vary LOADS! which means any timing you attempt is just not going to work.

Add more methods to your toggle class to separate things out:

void draw() … fine a,though I use void display() just to be different to the main draw()
void over() … checking if the touch/mouse is over the toggle … called from mousePressed()!
void mouseHandler() … to call if touch/mouse is over the toggle. Also use to trigger a single sendMidi etc…

Does that make sense?

EDIT 2:

So , in mousePressed() …if toggle.over() … call toggle.mouseHandler() to change change state
Use toggle.draw() ONLY to display the rects.


#94

i will send you cause for my case,i use mouse pressed inside every single toggle, so do not use void mousePressed, and i use a delay for clock related to frame rate.
Im unable to trig your clock outside void mouse Pressed


#95

@chanof

My clock isn’t really a clock as such … just an embedded timer in the playback thread. It’s the most minimal way I can think of of creating timing. Using clock threads and playback threads in sync is far more complicated so best avoided if not necessary!

EDIT:

In my example I just used an array of longs to hold nanosecond timing values. You could add a timing value to each toggle and use this in the same way … I am fiddling this myself with my app and I 'm not quite there yet, but it’s along the lines of (toggle is active) add it to the playback array and then use the play thread to play it using a play button!


#97

Not sure if you guys are aware, check the activity lifecycle to understand how to handle your objects during screen orientation changes or when the phone returns back on after it goes to sleep, for instance. You can check this:

https://github.com/processing/processing-android/wiki/Lifecycle-of-a-Processing-sketch

Now, since Android mode 4.0, PApplet is not an activity but a sketch. The life cycle is a bit different but not by much. You can still access the lifecycle’s functions.

For timing, you might want to spin a service. Or you could use the millis() function that Processing provides. I didn’t follow the whole discussion of why you decided to with a thread, as it was lengthly.

I hope this helps,

Kf


#98

@kfrajer
Yes I’m still looking at the lifecyle but simply using the manifest was just easy and it worked!

millis() doesn’t give good enough resolution for audio timing and using a new thread takes the processing away from draw() where the frame rate can vary a lot which causes all sorts of timing problems for audio playback.


#99

Hi! Does this code for thread can be less expensive for CPU?

// forum.processing.org/two/discussion/9707/
// creating-threads-in-processing
 
void setup() {
  size(400, 400, JAVA2D);
  frameRate(2);
  thread("myThread");
}
 
void draw() {
  background((color) random(#000000));
}
 
void myThread() {
  for (;; delay(1000))  print(mouseX + "\t");
}

I confess that i don’t understand what mean exactly “for (;; delay(1000))” so how to control it,
so two questions?
Could this be less expensive for CPU then the @shedMusic timer?
Could be used in the same way but with nano seconds?
I think that also with ms but less expensive calculation could be better for timing.
What you think?


#100

dela(1000) will pause everything for 1000mS. I can’t think how delay() could be of any use. The standard for midi timing is nonoseconds so it would be best to stick with that I think.

I need to run some tests


#101

@shedMusic
I wonder how you are implementing note-offs. I tried a bit yesterday, just with creating a second thread and doubling the cellLength, but that didn’t work so well. I’d like to have a slider controlling notelength, so after a specified amount of time after the note-on send the note off.
Maybe I didn’t fully understand the code. There were some note-offs but somewhere…
Today I implemented pattern saving using Table class. Not saving to a file yet, but that is easy to add. This is starting to be fun to use.


#102

@sensn

I’m not doing anything with note off yet … but it’s on my list. In fact I am currently thinking about a better way to playback the data … it’s quite wasteful checking all those empty cells in the grid for every loop of the pattern, so am going to have an array (or probably an ArrayList) that holds midi messages and just use it to send whatever data I like … basically a midi event list … and keep the playback thread just to do the absolute minimum necessary to send the data at the right time.


#103

@shedMusic

This sounds reasonable. I’m really looking forward to the results. I made my gui responsive like you suggested, and added pattern saving. So now i can go on implementing more CCs and Sysex-messages.
Pattern-based sequencing is great, feels a lot like Notator/Creator on Atari.:slightly_smiling_face: