package com.codefixia.audio;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import com.codefixia.drumcloud.DrumCloud;
import com.codefixia.drumcloud.R;
import processing.core.PApplet;
import de.humatic.nmj.NMJConfig;
import de.humatic.nmj.NMJSystemListener;
import de.humatic.nmj.NetworkMidiInput;
import de.humatic.nmj.NetworkMidiListener;
import de.humatic.nmj.NetworkMidiOutput;
import de.humatic.nmj.NetworkMidiSystem;
import de.humatic.nmj.NetworkMidiClient;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.AdapterView.OnItemSelectedListener;
/**
* Basic nmj sample application for Android.
* This shows how to initialize the system, create input
* and output ports, read and write MIDI etc.
* Please refer to the main readMe file to learn about general concepts behind nmj.
*
* You will need to make sure that the nmj.jar file is accessible (and gets exported).
* In Eclipse see Project / Properties / Java Build Path / Libraries.
*
* With nmj 0.86 the Android API level needs to be set to 12 or greater
* to build things or you will get compiler errors due to missing USB classes.
* Deployment is still possible on devices with lower API levels.
*/
public class Midi implements NetworkMidiListener, NMJSystemListener {
private NetworkMidiInput midiIn;
private NetworkMidiOutput midiOut;
private byte[] myNote = new byte[]{(byte)0x90, (byte)0x24, 0};
private MidiLogger midiLogger;
private NetworkMidiSystem nmjs;
private static DrumCloud drumCloud;
private Spinner spinner;
private ArrayAdapter<CharSequence> adapter;
private String[] channelArray;
public Midi(DrumCloud drumCloudRef) {
drumCloud=drumCloudRef;
try{
nmjs = NetworkMidiSystem.get(drumCloud);
} catch (Exception e) {
/*
* This would happen if no network permissions were given.
* See AndroidManifest.xml
*/
e.printStackTrace();
return;
}
NMJConfig.addSystemListener(this);
int connectivity = NMJConfig.getConnectivity(drumCloud);
if ((connectivity & NMJConfig.ADB) != 0 && NMJConfig.getNumChannels() < 4) {
/*
* Add a fourth channel to the default 3 if
* USB debugging is enabled. The desktop version
* of nmj can then be used to exchange MIDI over USB.
*/
NMJConfig.setNumChannels(4);
NMJConfig.setMode(3, NMJConfig.ADB);
}
}
public void showMidiTransportDialog(){
final Dialog dialog = new Dialog(drumCloud);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.midisetup);
//dialog.setTitle(R.string.about);
dialog.setCancelable(true);
final Button midiTestButton = (Button) dialog.findViewById(R.id.midiTestButton);
final Button midiSetupButton = (Button) dialog.findViewById(R.id.midiSetupButton);
/* can't use "this" in the below event handler */
final NetworkMidiListener ml = this;
spinner = (Spinner) dialog.findViewById(R.id.Spinner01);
channelArray = new String[NMJConfig.getNumChannels()];
for (int i = 0; i < NMJConfig.getNumChannels(); i++) channelArray[i] = NMJConfig.getName(i);
ArrayAdapter<CharSequence> adapter = new ArrayAdapter(dialog.getContext(), android.R.layout.simple_spinner_item, channelArray);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(
new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
/* using null instead of a specific client or listener removes all eventually attached clients and closes the port. */
try{ midiIn.close(null); } catch (NullPointerException ne){}
try{ midiOut.close(null); } catch (NullPointerException ne){}
try{
midiIn = nmjs.openInput(position, ml);
} catch (Exception e){
Log.e("MIDI IN","EX:"+e);
/* channel is output only */
}
midiTestButton.setEnabled(true);
try{
midiOut = nmjs.openOutput(position, ml);
} catch (Exception e){
Log.e("MIDI OUT","EX:"+e);
/* channel is input only */
midiTestButton.setEnabled(false);
}
}
public void onNothingSelected(AdapterView<?> parent) {}
});
midiTestButton.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent me) {
try{
if (me.getAction() == MotionEvent.ACTION_DOWN) {
myNote[2] = (byte)100;
midiOut.sendMidi(myNote);
midiTestButton.setPressed(true);
return true;
} else if (me.getAction() == MotionEvent.ACTION_UP) {
myNote[2] = 0;
midiOut.sendMidi(myNote);
midiTestButton.setPressed(false);
return false;
}
} catch (Exception ex){
Log.d("ONTOUCH","ex:"+ex.getStackTrace());
}
return true;
}
});
midiSetupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
final Intent si = new Intent(dialog.getContext(), de.humatic.nmj.NMJConfigDialog.class);
drumCloud.startActivity(si);
}
});
final TextView tv = (TextView) dialog.findViewById(R.id.TextView01);
midiLogger = new MidiLogger(tv);
dialog.show();
}
@Override
public void midiReceived(int channel, int ssrc, byte[] data, long timestamp) {
/*
* As MIDI does not arrive on the GUI thread, it needs to be offloaded in
* order to be displayed. Android's Handler class is one way to do this.
*/
Message msg = Message.obtain();
Bundle b = new Bundle();
b.putByteArray("MIDI", data);
b.putInt("CH", channel);
msg.setData(b);
midiLogger.sendMessage(msg);
}
public void sendNote(final int channel, final int pitch, final int velocity,int durationMS) {
if(Looper.myLooper() == Looper.getMainLooper()){
new Timer().schedule(new TimerTask() {
@Override
public void run() {
sendNoteOn(channel,pitch,velocity);
}
}, 0);
}else{
sendNoteOn(channel,pitch,velocity);
}
new Timer().schedule(new TimerTask() {
@Override
public void run() {
sendNoteOff(channel,pitch,velocity);
}
}, durationMS);
}
public void sendNoteOn(int channel, int pitch, int velocity) {
myNote[0] = (byte)144;
myNote[1] = (byte)(36+(pitch*2));
myNote[2] = (byte)(velocity);//(byte)(100);
//PApplet.println("Sending midi note:"+(36+pitch*2)+" velocity:"+velocity+" midiOut:"+(midiOut!=null)+" mainthread:"+(Looper.myLooper() == Looper.getMainLooper()));
try {
if(midiOut!=null)
midiOut.sendMidi(myNote);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void sendNoteOff(int channel, int pitch, int velocity) {
myNote[0] = (byte)128;
myNote[1] = (byte)(36+(pitch*2));
myNote[2] = 0;
try {
if(midiOut!=null)
midiOut.sendMidi(myNote);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static class MidiLogger extends android.os.Handler {
private StringBuffer sb = new StringBuffer();
private TextView tv;
private MidiLogger(TextView tv) {
super();
this.tv = tv;
}
public void handleMessage(android.os.Message msg) {
Bundle b = msg.getData();
sb.setLength(0);
byte[] data = b.getByteArray("MIDI");
sb.append("MIDI received: ");
StringBuffer lastMessage=new StringBuffer();
for (int i = 0; i < data.length; i++){
lastMessage.append((data[i] & 0xFF)+" ");
}
sb.append(lastMessage);
sb.append("\n");
tv.setText(sb.toString());
String[] midiParts=lastMessage.toString().split("\\s+");
//Log.d("midiParts","total:"+midiParts.length+":"+midiParts[0]);
if(midiParts.length==3){
int messageType=Integer.parseInt(midiParts[0]);
int pitch=Integer.parseInt(midiParts[1]);
int velocity=Integer.parseInt(midiParts[2]);
if(messageType>=128 && messageType<=143){
receiveNoteOff(messageType-127,pitch,velocity);
}else if(messageType>=144 && messageType<=159){
receiveNoteOn(messageType-143,pitch,velocity);
}else{
receiveControllerChange(messageType-175,pitch,velocity);
}
}
}
public void receiveNoteOn(int channel, int pitch, int velocity) {
// Receive a noteOn
/*DrumCloud.println();
DrumCloud.println("Note On:");
DrumCloud.println("--------");
DrumCloud.println("Channel:"+channel);
DrumCloud.println("Pitch:"+pitch);
DrumCloud.println("Velocity:"+velocity);*/
drumCloud.noteOn(channel, pitch, velocity);
}
public void receiveNoteOff(int channel, int pitch, int velocity) {
// Receive a noteOff
/*DrumCloud.println();
DrumCloud.println("Note Off:");
DrumCloud.println("--------");
DrumCloud.println("Channel:"+channel);
DrumCloud.println("Pitch:"+pitch);
DrumCloud.println("Velocity:"+velocity);*/
drumCloud.noteOff(channel, pitch, velocity);
}
public void receiveControllerChange(int channel, int number, int value) {
// Receive a controllerChange
DrumCloud.println();
DrumCloud.println("Controller Change:");
DrumCloud.println("--------");
DrumCloud.println("Channel:"+channel);
DrumCloud.println("Number:"+number);
DrumCloud.println("Value:"+value);
drumCloud.controllerChange(channel, number, value);
}
}
@Override
public void systemChanged(int channel, int property, int value) {
System.out.println(" System changed "+channel+" "+property+" "+value);
if (property == NMJConfig.RTPA_EVENT && value == NMJConfig.RTPA_CH_DISCOVERED) {
/*
* Given multicast works on your device then DNS might
* uncover more RTP channels and call this for notification.
* Newly found USB host channels will also be announced here.
* Time to update the spinner, which is not done in this sample.
* New channels will be available on the next launch.
*/
channelArray = new String[NMJConfig.getNumChannels()];
for (int i = 0; i < NMJConfig.getNumChannels(); i++){
channelArray[i] = NMJConfig.getName(i);
Log.d("NEW MIDI CHANNEL","->"+channelArray[i]);
}
adapter.notifyDataSetChanged();
}
}
@Override
public void systemError(int channel, int err, String description) {
Log.e("MIDI SYSTEM ERROR","Desc:"+description+" on channel:"+channel);
}
}