Arduino + Processing + Max / MSPで双方向にごにょごにょする

■ 仕様
- 全般
コミュニケーション方法:Arduino <-(Serial)-> Processing <-(OSC)-> Max / Msp


- Arduinoの仕様
ボーレート: 9600
センサ1: A5(可変抵抗)
センサ2: D2(チルトスイッチ)
LED1: D9(赤)
LED2: D10(緑)


- Processing側の仕様
シリアルのボーレート: 9600
OSC受信ポート: 8001


- Max / MSP側の仕様
IPアドレス: 127.0.0.1
OSC受信ポート: 8000


Arduino側のソースコード

/**
 * 一定間隔でセンサの値をおくりつつ、シリアルで値を受信したらその値に応じてLEDの色を変えるプログラム
 */
#define BAUD_RATE     9600
#define RESISTOR_PIN    A5
#define SWITCH_PIN       2
#define RED_LED_PIN      9
#define GREEN_LED_PIN   10
#define SEND_INTERVAL 1000

char messageValue[2][10];
int messagePosIndex = 0;
int messageValueIndex = 0;
unsigned long sendTimer = 0;

void setup() {
  Serial.begin(BAUD_RATE);
}

void loop () {
  getSerialData();
  
  // Send sensing data
  if (millis() - sendTimer > SEND_INTERVAL) {
    Serial.print(analogRead(RESISTOR_PIN)); // Potention Metor
    Serial.print("\t");
    Serial.print(digitalRead(SWITCH_PIN) == HIGH ? 255 : 0); // Tilt Switch
    Serial.println("\t");
  }
}

void getSerialData() {
  while (Serial.available() > 0) {
    int serialData = Serial.read();

    // Space
    if(serialData == 32) {
      if (messagePosIndex < 3) {
        messageValueIndex = 0;
        messagePosIndex++;
      }
    }
    // 0-9
    else if (serialData >= 48 && serialData <= 57) {
      if (messageValueIndex < 10) {
        messageValue[messagePosIndex][messageValueIndex++] = serialData;
      }
    }
    // Suffix(:)
    else if(serialData == 58) {
      int val0 = atoi(messageValue[0]);
      int val1 = atoi(messageValue[1]);

      // Change LED brightness
      analogWrite(RED_LED_PIN, val0);
      analogWrite(GREEN_LED_PIN, val1);

      for(int i=0;i<messagePosIndex;i++) {
        for(int j=0;j<10;j++) {
          messageValue[i][j] = 0;
        }
      }
      messagePosIndex   = 0;
      messageValueIndex = 0;
    }
  }
}


■ Processing側のソースコード

/**
 * Arduinoからの信号をOSC経由でMaxに送るプログラム
 * いちおう、MaxからOSC経由で信号が来た場合、Arduinoに送り返すようにもなっている
 */
import processing.serial.*;
import processing.net.*;
import oscP5.*;
import netP5.*;

String COM_PORT = "";
String OSC_TARGET_IP_ADDRESS = "127.0.0.1";
int BAUD_RATE = 9600; // ArduinoのBaud Rate
int OSC_MY_PORT = 8001; // Processingの受信用ポート
int OSC_TARGET_PORT = 8000; // Max側の受信用ポート

Serial serialPort;
OscP5 oscP5;
NetAddress maxLocation;
String recievedMessage = "";
int dumpY = -15;
int lastDatas[];

void setup() {
  size(640, 480);
  background(0);
  smooth();
  println(Serial.list());

  // OSC
  try {
    // increase the datagram size to 10000 bytes
    // by default it is set to 1536 bytes
    OscProperties oscProperties = new OscProperties();
    oscProperties.setDatagramSize(10000); 
    oscProperties.setListeningPort(OSC_MY_PORT);
   
    oscP5 = new OscP5(this, oscProperties);
    maxLocation = new NetAddress(OSC_TARGET_IP_ADDRESS, OSC_TARGET_PORT);
  }
  catch (Exception e) {
    dump("OSC initialized error:" + e);
  }

  // Serial
  try {
    // シリアルポートをしていない場合、8番当たりを狙ってみる
    if (COM_PORT.equals("")) {
      COM_PORT = Serial.list()[8];
    }
    serialPort = new Serial(this, COM_PORT, BAUD_RATE);
  }
  catch (Exception e) {
    dump("Serial initialized error:" + e);
  }
  
  // とりあえず5個くらい要素用意しとく
  lastDatas = new int[5];
}

void draw() {

}

/**
 * OSCの信号受信時のイベント
 * 受信フォーマットは、「/sensor ,ii 0 1」といった感じを想定。
 */
void oscEvent(OscMessage receivedMessage) {
  // OSCのフォーマット確認
  String message = "### received an osc message." + 
    " addrpattern: " + receivedMessage.addrPattern() + 
    " typetag: " + receivedMessage.typetag() + 
    " timetag: " + receivedMessage.timetag();
  dump(message);

  // OSCから受け取ったデータをArduinoに送る
  String cmd = receivedMessage.get(0).intValue() + " " + receivedMessage.get(1).intValue() + ":";
  dump(cmd);
  serialPort.write(cmd);
}

/**
 * シリアル(Arduinoから来る想定)の信号受信時のイベント
 * 受信フォーマットは、「100\t200:\r\n」といった感じを想定。
 */
void serialEvent(Serial serialData) {
  String val = serialPort.readString();
  if (val.equals("\r")) {
    parseData(recievedMessage);
    recievedMessage = "";
  }
  else if (val.equals("\n")) {
  }
  else {
    recievedMessage = recievedMessage + val;
  }
}

/**
 * 受信したシリアルデータをパースして、OSCでMax / MSPに送る
 * 送信フォーマットは、「/msg , ii 10 20」といった感じを想定。
 */
void parseData(String message) {
  dump(recievedMessage);
  try {
    String[] datas = recievedMessage.split("\t");
    OscMessage msg = new OscMessage("/msg");
    for (int i=0;i<datas.length;i++) {
      int v = int(datas[i]);
      msg.add(v);
      lastDatas[i] = v;
    }
    oscP5.send(msg, maxLocation);
  }
  catch (Exception e) {
    dump("[Error][parseData]" + e);
  }
}

////////////////////////////////// utils //////////////////////////////////

/**
 * ダンプ
 */
void dump(String val) {
  // GUIウィンドウに表示する場合
  //int y = _incrementDumpY();
  //text(val + "\n", 0, y, WINDOW_WIDTH, y + 20);

  println(val);
}

/**
 * ウィンドウに表示する際のテキストの位置を指定
 */
int _incrementDumpY() {
  dumpY += 15;
  if (dumpY > 480) {
    background(0);
    dumpY = -15;
  }
  return dumpY;
}

/**
 * 日付を取得
 */
String getTime() {
  return String.format("%04d/%02d/%02d %02d:%02d:%02d",
  year(), 
  month(),
  day(),
  hour(),
  minute(),
  second());
}


■ Max / MSP側のソースコード(json形式でexportしてみた)

{
	"patcher" : 	{
		"fileversion" : 1,
		"appversion" : 		{
			"major" : 6,
			"minor" : 1,
			"revision" : 3,
			"architecture" : "x86"
		}
,
		"rect" : [ 29.0, 69.0, 965.0, 600.0 ],
		"bglocked" : 0,
		"openinpresentation" : 0,
		"default_fontsize" : 12.0,
		"default_fontface" : 0,
		"default_fontname" : "Arial",
		"gridonopen" : 0,
		"gridsize" : [ 15.0, 15.0 ],
		"gridsnaponopen" : 0,
		"statusbarvisible" : 2,
		"toolbarvisible" : 1,
		"boxanimatetime" : 200,
		"imprint" : 0,
		"enablehscroll" : 1,
		"enablevscroll" : 1,
		"devicewidth" : 0.0,
		"description" : "",
		"digest" : "",
		"tags" : "",
		"boxes" : [ 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"frgb" : 0.0,
					"id" : "obj-7",
					"maxclass" : "comment",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 503.0, 40.0, 157.0, 20.0 ],
					"text" : "P5からの信号モニタリング"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-61",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 575.0, 209.0, 33.0, 20.0 ],
					"text" : "s v3"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-59",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 539.0, 209.0, 33.0, 20.0 ],
					"text" : "s v2"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-36",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 503.0, 209.0, 33.0, 20.0 ],
					"text" : "s v1"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-8",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 646.0, 117.0, 79.0, 20.0 ],
					"text" : "print oscmsg"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-5",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 3,
					"outlettype" : [ "int", "int", "int" ],
					"patching_rect" : [ 503.0, 164.0, 79.0, 20.0 ],
					"text" : "unpack 1 1 1"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-3",
					"maxclass" : "newobj",
					"numinlets" : 2,
					"numoutlets" : 2,
					"outlettype" : [ "", "" ],
					"patching_rect" : [ 503.0, 128.0, 67.0, 20.0 ],
					"text" : "route /msg"
				}

			}
, 			{
				"box" : 				{
					"id" : "obj-2",
					"maxclass" : "button",
					"numinlets" : 1,
					"numoutlets" : 1,
					"outlettype" : [ "bang" ],
					"patching_rect" : [ 588.0, 117.0, 20.0, 20.0 ]
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-6",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 1,
					"outlettype" : [ "" ],
					"patching_rect" : [ 503.0, 78.0, 99.0, 20.0 ],
					"text" : "udpreceive 8000"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"frgb" : 0.0,
					"id" : "obj-1",
					"maxclass" : "comment",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 121.0, 34.0, 157.0, 20.0 ],
					"text" : "これおすと信号おくられる"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"frgb" : 0.0,
					"id" : "obj-29",
					"maxclass" : "comment",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 379.5, 34.0, 82.0, 20.0 ],
					"text" : "green led"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"frgb" : 0.0,
					"id" : "obj-28",
					"maxclass" : "comment",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 304.5, 34.0, 53.5, 20.0 ],
					"text" : "red led"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-27",
					"maxclass" : "flonum",
					"numinlets" : 1,
					"numoutlets" : 2,
					"outlettype" : [ "float", "bang" ],
					"parameter_enable" : 0,
					"patching_rect" : [ 308.0, 242.0, 50.0, 20.0 ]
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-26",
					"maxclass" : "flonum",
					"numinlets" : 1,
					"numoutlets" : 2,
					"outlettype" : [ "float", "bang" ],
					"parameter_enable" : 0,
					"patching_rect" : [ 383.0, 242.0, 50.0, 20.0 ]
				}

			}
, 			{
				"box" : 				{
					"id" : "obj-22",
					"maxclass" : "gain~",
					"numinlets" : 2,
					"numoutlets" : 2,
					"outlettype" : [ "signal", "int" ],
					"parameter_enable" : 0,
					"patching_rect" : [ 379.5, 64.0, 22.0, 140.0 ],
					"size" : 255
				}

			}
, 			{
				"box" : 				{
					"id" : "obj-21",
					"maxclass" : "gain~",
					"numinlets" : 2,
					"numoutlets" : 2,
					"outlettype" : [ "signal", "int" ],
					"parameter_enable" : 0,
					"patching_rect" : [ 304.5, 64.0, 22.0, 140.0 ],
					"size" : 255
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-18",
					"maxclass" : "message",
					"numinlets" : 2,
					"numoutlets" : 1,
					"outlettype" : [ "" ],
					"patching_rect" : [ 228.0, 64.0, 50.0, 18.0 ],
					"text" : "/sensor"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-16",
					"maxclass" : "newobj",
					"numinlets" : 4,
					"numoutlets" : 1,
					"outlettype" : [ "" ],
					"patching_rect" : [ 301.0, 292.0, 68.0, 20.0 ],
					"text" : "pack s i i i"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 12.0,
					"id" : "obj-13",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 322.0, 327.0, 92.0, 20.0 ],
					"text" : "print osc_msg"
				}

			}
, 			{
				"box" : 				{
					"fontname" : "Arial",
					"fontsize" : 9.0,
					"id" : "obj-4",
					"maxclass" : "newobj",
					"numinlets" : 1,
					"numoutlets" : 0,
					"patching_rect" : [ 301.0, 365.0, 113.0, 17.0 ],
					"text" : "udpsend 127.0.0.1 8001"
				}

			}
 ],
		"lines" : [ 			{
				"patchline" : 				{
					"destination" : [ "obj-13", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 310.5, 324.0, 331.5, 324.0 ],
					"source" : [ "obj-16", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-4", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 310.5, 312.0, 310.5, 312.0 ],
					"source" : [ "obj-16", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-16", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 237.5, 279.0, 310.5, 279.0 ],
					"source" : [ "obj-18", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-27", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 317.5, 204.0, 317.5, 204.0 ],
					"source" : [ "obj-21", 1 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-26", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 392.5, 204.0, 392.5, 204.0 ],
					"source" : [ "obj-22", 1 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-16", 2 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 392.5, 279.0, 343.166656, 279.0 ],
					"source" : [ "obj-26", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-16", 1 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 317.5, 279.0, 326.833344, 279.0 ],
					"source" : [ "obj-27", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-5", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 512.5, 155.5, 512.5, 155.5 ],
					"source" : [ "obj-3", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-36", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 512.5, 186.0, 512.5, 186.0 ],
					"source" : [ "obj-5", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-59", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 542.5, 195.0, 548.5, 195.0 ],
					"source" : [ "obj-5", 1 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-61", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 572.5, 195.0, 584.5, 195.0 ],
					"source" : [ "obj-5", 2 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-2", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 512.5, 114.0, 597.5, 114.0 ],
					"source" : [ "obj-6", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-3", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 512.5, 99.0, 512.5, 99.0 ],
					"source" : [ "obj-6", 0 ]
				}

			}
, 			{
				"patchline" : 				{
					"destination" : [ "obj-8", 0 ],
					"disabled" : 0,
					"hidden" : 0,
					"midpoints" : [ 512.5, 99.0, 655.5, 99.0 ],
					"source" : [ "obj-6", 0 ]
				}

			}
 ],
		"dependency_cache" : [  ]
	}

}