召喚らくがき
モンスターズ“魔法の書” デバイスで熱戦!
ココノヱ『召喚!! らくがきモンスターズ』

ココノヱ

“魔法の書” デバイスで熱戦!
ココノヱ『召喚!! らくがきモンスターズ』

Play!

「Intel® Edison」× 日本屈指の表現者たちーー両者の融合から始まる、かつてない “未来体験” へ、ようこそ!
8/1(土)、2(日)の2日間にわたって開催された、最先端の “ものづくり” の祭典『Maker Faire Tokyo 2015』(MFT 2015)。その一角、Intelブースの目玉企画のひとつとして、最前線の作り手たちによる展示企画『Artist Showcase』の作品が、今年もその全貌を現した。
過去3回にわたって5組の参加アーティストたちに密着し、発想のヒントや完成へと至る道のりをお届けしてきたこのレポート。
ついにお披露目を迎えた彼らの作品は、MFT 2015の会場でどんな光を放ったのか。彼らのチャレンジ精神と最新テクノロジーにかける想いを、システム構成やソースコードとともに解き明かしていこう……!

岡山発、“最先端の魔法体験”
—Intelブースにココノヱあり!

いまや大手メーカー以外でも、独自技術にデザインやユーザビリティを兼ね備えた、高クオリティの “ものづくり” を実現できる時代。とはいえ、それが “製品レベル” に達しているかというと、Makerムーブメントはまだまだこれから……だと思っていた。ココノヱの作品『召喚!! らくがきモンスターズ』を体験するまでは! 一見して即座に、「すわ、アーケードゲームか!?」と見間違うほどの完成度。 体験者はまず、特製のカードにモンスターの絵を描く。それを “魔法の書” 型のデバイスにかざして「召喚!」と唱えると、自作のモンスターがモニターの中に出現して、対戦バトルがスタート。「パンチ!」「バリア!」などのコマンドを声に出して唱えたり、“魔法の書” にかざした手を動かして「ファイアーボール!」「ハリケーン・ブラスト!」といった魔法技を繰り出したり。 子どもから大人まで、誰もが我を忘れて心奪われた、まさに “最先端の魔法体験” 。岡山を拠点に活動するココノヱの代表取締役でデザイナーの宗 佳広(そう・よしひろ)、テクニカルディレクターの山田純也、クリエイティブエンジニアの長洞龍生(ながほら・りゅうき)に、会場での感触を聞いてみた。

宗 佳広
予想はしていましたが、まさかこんなに多くのお客さんが集まって、ここまで夢中になってくれるとは……。これまでの取材でもお話ししたとおり、どんな形を描いても手や足っぽいところを認識し、それらしく動かすことができる技術は僕たちの独自技術ですが、また新しい手応えを感じています。
山田純也
と同時に、これだけ音や通信が飛び交っている会場だということ、世代を超えた方々に体験していただいたことで、多くの課題が見つかりました。子どもの声は音域的に認識しにくいことがわかり、マイクで拾った言葉を受けて技を出やすくするなど、会場で微調整を続けています。
長洞龍生
その一方で、子どもたちが素直に “魔法の書” デバイスを受け容れて、ゲームに熱中している様子に心打たれました。技術的な仕掛けを感じさせずに、誰もが夢中になれるだけの世界観を表現できたのではないかと思います。

自分の描いたモンスターを召喚する “魔法の書” ——
『召喚らくがきモンスターズ』の仕組みを大公開!

システムは大きくふたつに分かれている。
まず、1つめの中心となっているのは Edison で、 Node.js アプリといくつかの子プロセスを起動している。 Edison に接続されているのは 6つの赤外線距離センサー、RGB LED、USB Webカメラ、マイクと接続用のUSBオーディオ・アダプターケーブルだ。これが1セットとなって、対戦用には2セットが用意された。

2つめは、コンピュータ内のゲームのシステム部分だ。ゲーム部分がプレーヤーの2つの Edisonからデータを受け取り、スクリーン上にモンスターを生み出し、モンスターのアクションを制御し、対戦の音楽や効果音を発する。ゲーム用のコンピュータ側もまた、Edison に対して撮影命令(絵のスキャン)、音声認識起動命令やLED制御命令などのコマンドを送っている。

まず、プレーヤーは事前に用意されたカードにオリジナルのモンスターを描く。カードには火、水、木、光、闇と、それぞれテーマがあり、それによって繰り出す技にも変化が出るようになっている。

プレーヤーは描いたモンスターのカードの絵の部分を下に向けて、「魔法の書」のアクリル部分に置く。その時、魔法の書の中に設置された距離センサーがカードを感知する(カードセンサー)。
音声認識プログラム「Julius」が起動され、マイクからの音声を文字に変換し、メインのNodeアプリに送る。すべての音声によるコマンド(命令)はJuliusを介して、文字に変換され、「召喚」「ジャンプ」など、辞書ファイルに事前に登録されているコマンド(カスタムされた定義済みのコマンド)と照らし合わされる。コマンドの一致の精度をより高めるため、辞書ファイルには、1つのコマンドに対して、いくつもの異なる発音が含まれている。NodeアプリはOSC(Open Sound Control)ライブラリを使って、距離センサーとJuliusからのデータを処理し、ゲーム用のコンピュータに送る役割を果たしている。

プレーヤーがマイクに向かって「召喚!」と唱えると、それがトリガーとなって魔法の書に内蔵されたLEDが光を放ち、暗い本の中でカードも照らされ、同時に、これも魔法の書に内蔵されたWebカメラを使ってカードがスキャンされるのだ。

ここでスキャンされた画像はOpenCV アプリで処理される。このアプリが斜めの状態でスキャンされた画像をまっすぐな状態に直し、Edison に画像を保存。それがゲーム用PCに送られ、ゲーム用コンピュータで処理され、モンスターの部分のみが切り取られる。このモンスターがゲーム内に現れ、命が与えられたように動くようになるのだ。

2つの対戦するモンスターが作られ、スクリーン内に現れれば、ゲームが開始する。
対戦中は FadeCandyServer 子プロセスが FadeCandy LEDを制御し、魔法の書内のLEDの色を変えるなどして、観客に臨場感を与えている。プレーヤーは魔法の書の右部分に手を当てることで自身のモンスターを左右にコントロールすることができる。ここには、5つの赤外線距離センサー(手かざしセンサー)が入っており、手の位置を検出している。この5つのセンサーから受け取ったデータはEdison 上のNodeアプリで処理され、手の位置や動きを判断している。 Edison からのデータがゲーム用コンピュータに送られ、手の動きに応じてキャラクターを動かしている。

さらに、プレーヤーは「パンチ!」「バリア!」などのコマンドを唱えることでモンスターを操ることができる。辞書ファイルに入っているコマンドと一致すると、 Edison からゲーム用コンピュータに指示が送られ、モンスターがその通りにアクションするというわけだ。

「パンチ!」「キック!」など基本の攻撃に加えて、同じ技術を応用させたのが「ブリザード・キャノン!」といった必殺技だ(必殺技は火、水、木、光、闇などカードの特性によって異なる)。必殺技を使うには、呪文を唱える音声が認識された後、スクリーンに現れるコマンドに合わせて魔法の書の上で指示通りに手を動かす必要がある。指示通りにコマンドを出せれば、晴れて敵対するモンスター目がけて必殺技が繰り出される。

材料

  • インテル® Edison キット For Arduino
  • Fade Candy Neopixel LED ドライバー
  • NeoPixel LED
  • マイク
  • USB Webカメラ(Logicool)
  • USB オーディオ・インターフェイス
  • USB ハブ
  • USB イーサネット・アダプター
  • USB ケーブル
  • イーサネット・ケーブル
  • いくつかのワイヤー
  • 手かざし用:赤外線距離センサー 5個 (シャープ測距モジュール GP2Y0E03)
  • ブレッドボード
  • カード検知用:赤外線距離センサー 1個 (フォトリフレクタ(反射タイプ) LBR-127HLD)
  • 本型のケース(魔法書となるもの)
  • ゲームのプログラムを作動させるコンピュータ
  • 巨大モニター
  • キャラクターを描くためのカード

ソースコード

Edison 周辺のソースコードは GitHub でダウンロード可能。
また Edison にJulius、Open CV、その他いくつかの Nodeモジュールのインストールが必要。
詳細はGitHub内の手順をご覧いただきたい。

Learn More on GitHub

/_node/_main.js
メインとなるNodeアプリ
                var _express	= require("express");
var _osc 		= require("node-osc");
var _process	= require("child_process");
var _config		= require("./config.js");
var _camera		= require("./camera.js");
var _julius		= require("./julius.js");

var _oscClient, _oscServer;
var _app;
var _loopDuration;

// 各種センサーとデバイス
var _CardSensor  = require("./sensors/card_detect_sensor.js");
var _HandSensor  = require("./sensors/ir_distance_sensor.js");
var _NeoPixelLED = require("./led/neopixel.js");

var _fadeCandyServer = null;
var _led = null;
var _cardSensor;
var _handSensor;

var _isRunningCardSensor = false;
var _isRunningHandSensor = false;
var _exitFlag = false;



/**
 * main
 */
module.exports.main = function()
{
	_app = _express();

	//
	// fcserver-galileoの起動
	_fadeCandyServer = _process.spawn( "./_fadecandy/fcserver-galileo" );

	_fadeCandyServer.stdout.on( "data", function( data )
	{
		console.log("[FadeCandyServer.stdout]:" + data );
	});

	// Close時
	_fadeCandyServer.on( "close", function( code )
	{
		console.log( "FadeCandyServer is CLOSE: [code->]" + code );
	});

	_config.init();
	_camera.init();
	_loopDuration = 1000 / _config.FPS;

	_initSensors();
	_initOSC();
	_initServer();

	setTimeout( _mainLoop, _loopDuration );


	// 終了シグナルを見てアプリを終了させる
	var signals = ['SIGINT', 'SIGHUP', 'SIGTERM'];
	for( var i = 0; i < 3; ++i )
	{
		process.on( signals[i], function()
		{
			console.log("app is CLOSING...");

			// FadeCandyの終了前にLED全消し。ダメ。
			// _led.allOff();

			_exitFlag = true;

			setTimeout( function(){
				console.log("[close fade Candy]");
				_fadeCandyServer.kill('SIGINT');
			}, 1000 );


			if( !_exitFlag && _julius != null )
			{
				console.log( ">> close Julius" );
				_julius.stop();
			}


			if( !_exitFlag && _camera != null )
			{
				console.log( ">> close Camera" );
				_camera.stop();
			}

			// 3秒ぐらい待ってから終了させる
			setTimeout( function(){
				console.log("[close node APP]");
				process.exit(1);
			}, 3000 );
		});
	}
};



/**
 * メインループ
 */
function _mainLoop()
{
	if( _exitFlag ) return;

	if( _isRunningCardSensor )
	{
		// カードセンサの値を送る
		_cardSensor.sensing();
		_oscClient.send( "/cardsensor_" + _config.ID, _cardSensor.getValue() );
	}

	if( _isRunningHandSensor )
	{
		// 手かざしセンサの値を送る
		_handSensor.sensing();

		var data = {
			 val00:_handSensor.getValue00()
			,val01:_handSensor.getValue01()
			,val02:_handSensor.getValue02()
			,val03:_handSensor.getValue03()
			,val04:_handSensor.getValue04()
		};
		var jsonStr = JSON.stringify( data );

		_oscClient.send( "/handsensor_" + _config.ID, jsonStr );
	}

	// LEDの更新
	_led.clock();

	setTimeout( _mainLoop, _loopDuration )
}


/**
 * センサ類初期化
 */
function _initSensors()
{
	// 引数はピンの番号
	_handSensor = new _HandSensor( 0, 1, 2, 3, 4 );
	_cardSensor = new _CardSensor( 5 );

	// 16 : 合計個数, 4 : 照明用の個数
	_led = new _NeoPixelLED( 16, 4, _loopDuration );
	_led.allOff();
}


/**
 * OSC接続
 */
function _initOSC()
{
	_oscClient = new _osc.Client( _config.PC_ADDRESS, _config.OSC_PORT );
}


/**
 * サーバー起動
 */
function _initServer()
{
	// staticファイル利用
	_app.use( _express.static("public") );

	// POST利用
	var bodyParser = require("body-parser");
	_app.use( bodyParser.urlencoded({extended: true}) );

	_app.listen( _config.SERVER_PORT );

	_app.get("/check_connection", _checkConnection );
	_app.get("/get_config", _getConfig );

	_app.post("/set_config", _setConfig );
	_app.post("/ctrl_camera", _ctrlCamera );
	_app.post("/ctrl_julius", _ctrlJulius );
	_app.post("/ctrl_card_sensor", _ctrlCardSensor );
	_app.post("/ctrl_hand_sensor", _ctrlHandSensor );
	_app.post("/ctrl_camera_led", _ctrlCameraLED );
	_app.post("/ctrl_txt_led", _ctrlTxtLED );
	_app.post("/ctrl_circle_led", _ctrlCircleLED );
}


/**
 * [ /check_connection ]
 * 接続しているかどうか見て返します。
 */
function _checkConnection( req, res )
{
	res.send( "SUCCESS" );
}


/**
 * [ /get_config ]
 * 設定用のJSONデータを取得して返します。
 */
function _getConfig( req, res )
{
	var jsonStr = JSON.stringify( _config.getConfigData(), null );
	res.send( jsonStr );
}


/**
 * [ /set_config ]
 * カメラ設定用のJSONを更新し、カメラに設定変更を投げる。
 */
function _setConfig( req, res )
{
	var body = req.body;

	_config.updateConfig( JSON.parse( String( body["jsonStr"] ) ) );
	_camera.updateConfig();

	res.send( "SUCCESS" );
}


/**
 * [ /ctrl_camera ]
 * カメラを制御します。
 */
function _ctrlCamera( req, res )
{
	var code = String( req.body["data"] );
	console.log( "ctrlCamera :" + code );

	var msg = "";

	switch ( code )
	{
		case "start":
			msg = _camera.start();
			res.send( msg );
			break;

		case "stop":
			msg = _camera.stop();
			res.send( msg );
			break;

		case "scan":
			msg = _camera.scan( function()
			{
				res.send( "SCAN_COMPLETE" );
			});

			if( msg == "FAILED" ) res.send( "FAILED" );
			break;

		case "scan_debug":
			msg = _camera.scanWithDebug( function()
			{
				res.send( "SCAN_WITH_DEBUG_COMPLETE" );
			});

			if( msg == "FAILED" ) res.send( "FAILED" );
			break;

		default :
			msg = "NO_PARAMS";
			res.send( msg );
			break;
	}
}


/**
 * [/ctrl_julius]
 * 音声認識の開始・停止など
 */
function _ctrlJulius( req, res )
{
	var code = String( req.body["data"] );
	console.log( "ctrlJulius :" + code );

	var codes = code.split("_");
	var msg = "";

	switch ( codes[0] )
	{
		case "start":
			var conf = _config.getConfigData();
			var micConf = conf["micConfigs"];

			msg = _julius.start(
				micConf["cardID"],
				micConf["subDeviceID"],
				micConf["lv"],
				micConf["rejectShort"],
				codes[1],
				_onDetectWordsHandler
			);

			res.send( msg );
			break;

		case "stop":
			msg = _julius.stop();
			res.send( msg );
			break;

		default :
			res.send( "NO_PARAMS" );
			break;
	}
}


/**
 * Juliusの音声認識後に実行されるイベントハンドラ
 */
function _onDetectWordsHandler( data )
{
	// console.log( "[" + _config.ID + "]DETECT Words:" + data );

	// 長すぎる出力が来たら無視...
	if( data.length < 1000 )
	{
		try
		{
			_oscClient.send( "/julius_" + _config.ID, String(data));
		}
		catch(err)
		{
			console.log("[Error!] onDetectWordHandler");
		}
	}
	else
	{
		console.log("output is TOO LONG");
	}
}


/**
 * [/ctrl_card_sensor]
 * カードセンサの制御
 */
function _ctrlCardSensor( req, res )
{
	var code = String( req.body["data"] );

	console.log( "ctrlCardSensor :" + code );

	switch ( code ) {
		case "start":
			_isRunningCardSensor = true;
			res.send( "CARD_SENSOR_ON" );
			break;

		case "stop":
			_isRunningCardSensor = false;
			res.send( "CARD_SENSOR_OFF" );
			break;

		default :
			res.send( "NO_PARAMS" );
			break;
	}
}


/**
 * [/ctrl_hand_sensor]
 * 手かざしセンサの制御
 */
function _ctrlHandSensor( req, res )
{
	var code = String( req.body["data"] );

	console.log( "ctrlHandSensor :" + code );

	switch ( code ) {
		case "start":
			_isRunningHandSensor = true;
			res.send( "HAND_SENSOR_ON" );
			break;

		case "stop":
			_isRunningHandSensor = false;
			res.send( "HAND_SENSOR_OFF" );
			break;

		default :
			res.send( "NO_PARAMS" );
			break;
	}
}


/**
 * [/ctrl_camera_led]
 * カメラ照明用LEDの制御
 */
function _ctrlCameraLED( req, res )
{
	var code = String( req.body["data"] );
	console.log( "LED_CTRL:" + code );
	var order = JSON.parse( code );

	var r = order["r"];
	var g = order["g"];
	var b = order["b"];
	var brightnessList = order["brightness"];

	if( r != null && g != null && b != null && brightnessList != null )
	{
		console.log( "[CAMERA LED] > R:" + r, "G:" + g, "B:" + b, "brightness:" + brightnessList );
		_led.controlCamPixelRgb( 0, r, g, b, brightnessList[0], 0.5 );
		_led.controlCamPixelRgb( 1, r, g, b, brightnessList[1], 0.5 );
		_led.controlCamPixelRgb( 2, r, g, b, brightnessList[2], 0.5 );
		_led.controlCamPixelRgb( 3, r, g, b, brightnessList[3], 0.5 );
	}

	res.send( "ok" );
}


/**
 * [/ctrl_txt_led]
 * 文字用LEDの制御
 */
function _ctrlTxtLED( req, res )
{
	var code = String( req.body["data"] );
	var order = JSON.parse( code );

	var pxList = order["list"];
	var duration = order["duration"];

	if( pxList != null && duration != null )
	{
		for( var i = 0, len = 4; i < len; ++i )
		{
			var px = pxList[i];
			_led.controlPixel( px["index"], px["h"], px["s"], px["v"], duration );
		}
	}

	res.send( "ok" );
}



/**
 * [/ctrl_circle_led]
 * 魔法陣用LEDの制御
 */
function _ctrlCircleLED( req, res )
{
	var code = String( req.body["data"] );
	var order = JSON.parse( code );

	var pxList = order["list"];
	var duration = order["duration"];

	if( pxList != null && duration != null )
	{
		for( var i = 0, len = 8; i < len; ++i )
		{
			var px = pxList[i];
			_led.controlPixel( px["index"], px["h"], px["s"], px["v"], duration );
		}
	}

	res.send( "ok" );
}
                
              
/_node/sensors/ir_distance_sensor.js
手かざしを検知するためのモジュール
                var mraa = new require('mraa');

/**********************************************
 * 手かざしを検知するためのモジュール
 * 赤外線距離センサー GP2Y0E03
 * http://akizukidenshi.com/catalog/g/gI-07547/
 * ・測距範囲:4〜50cm
 * ・sensing()をsetInterval()にセットして下さい。
 * ・getValue()で5つの各センサーの値を取得することができます。
 **********************************************/


/*
	プロパティ
*/
var _sensorPin00;
var _sensorPin01;
var _sensorPin02;
var _sensorPin03;
var _sensorPin04;
var _value00 = 0;
var _value01 = 0;
var _value02 = 0;
var _value03 = 0;
var _value04 = 0;


/*
	コンストラクタ
*/
var IRDistanceSensor = function(sensorPin00Num, sensorPin01Num, sensorPin02Num, sensorPin03Num, sensorPin04Num){

	if (sensorPin00Num == null) sensorPin00Num = 0;
	if (sensorPin01Num == null) sensorPin01Num = 1;
	if (sensorPin02Num == null) sensorPin02Num = 2;
	if (sensorPin03Num == null) sensorPin03Num = 3;
	if (sensorPin04Num == null) sensorPin04Num = 4;

	_sensorPin00 = new mraa.Aio(sensorPin00Num);
	_sensorPin01 = new mraa.Aio(sensorPin01Num);
	_sensorPin02 = new mraa.Aio(sensorPin02Num);
	_sensorPin03 = new mraa.Aio(sensorPin03Num);
	_sensorPin04 = new mraa.Aio(sensorPin04Num);
};


/*
	値を出力メソッド
*/
IRDistanceSensor.prototype.getValue00 = function(){

	return _value00;
};
IRDistanceSensor.prototype.getValue01 = function(){

	return _value01;
};
IRDistanceSensor.prototype.getValue02 = function(){

	return _value02;
};
IRDistanceSensor.prototype.getValue03 = function(){

	return _value03;
};
IRDistanceSensor.prototype.getValue04 = function(){

	return _value04;
};


/*
	検知メソッド
	setInterval関数にセットして下さい。
*/
IRDistanceSensor.prototype.sensing = function(){

	var val00 = _sensorPin00.read();
	_value00 = val00;
	// _value00 = 0; // not ready
	var val01 = _sensorPin01.read();
	_value01 = val01;
	var val02 = _sensorPin02.read();
	_value02 = val02;
	var val03 = _sensorPin03.read();
	_value03 = val03;
	var val04 = _sensorPin04.read();
	_value04 = val04;
};


module.exports = IRDistanceSensor;
                
              
/_node/sensors/card_detect_sensor.js
フォトインタラプタを使用してカードの挿入を検知するモジュール
                var mraa = require('mraa');

/**********************************************
 * フォトインタラプタを使用してカードの挿入を検知するモジュール
 * sensing()をsetInterval()にセットして下さい。
 * getValue()でセンサーの値を取得することができます。
 * getAtate()でカードが入っているかどうかのbool値を取得することができます。
 **********************************************/


/*
	プロパティ
*/
var _sensorPin;
var _threshold;
var _hysteresis;
var _value = 0;
var _state = false;


/*
	コンストラクタ
*/
var CardDetectSensor = function(sensorPinNum, threshold, hysteresis){

	if (sensorPinNum == null) sensorPinNum = 1;
	if (threshold == null) _threshold = 10;
	else _threshold = threshold;
	if (hysteresis == null) _hysteresis = 2;
	else _hysteresis = hysteresis;

	_sensorPin = new mraa.Aio(sensorPinNum);
};


/*
	値出力メソッド
*/
CardDetectSensor.prototype.getValue = function(){

	return _value;
};


/*
	検知結果出力メソッド
*/
CardDetectSensor.prototype.getState = function(){

	return _state;
};


/*
	検知メソッド
	setInterval関数にセットしてください。
*/
CardDetectSensor.prototype.sensing = function(){

	_value = _sensorPin.read();
	this.judgement();
};


/*
	検出判定メソッド
*/
CardDetectSensor.prototype.judgement = function(){

	if (_value < (_threshold + _hysteresis))
	{
		_state = true;
	}
	else if (_value > (_threshold - _hysteresis))
	{
		_state = false;
	}
	else
	{
	}
};


module.exports = CardDetectSensor;
                
              
/_node/led/neopixel.js
NeoPixel LEDを光らすためのモジュール
                var OPC = new require('./opc');
var client = new OPC('localhost', 7890);

/**********************************************
 * NeoPixelLEDを光らすためのモジュール
 * ・clock()をsetInterval()にセットしてください。
 * ・コンストラクタの第三引数には呼び出し元のsetIntervalのdelayをセットして下さい。
 * ・カメラ用照明LEDの制御はControlCamLight()を使用して下さい。
 * ・演出用LEDの制御はControlDirection()を使用して下さい。
 * ・LEDの個別制御はcontrolPixel()を使用して下さい。
 **********************************************/


/*
	プロパティ
*/
//
// ピクセル(LED)の全体個数
var _ledTotalNum = 10;

//
// 演出に使うピクセル
var _ledDirectionNum = 0;

//
// カメラ照明に使うピクセル
var _ledCamNum = 4;

//
// このモジュールの定義元のdelay
var _setIntervalDelay = 1000 / 30;

//
// 各LEDピクセルの現在の情報
var _pixelsInfo = [];

//
// 各LEDピクセルの変化量
var _pixelsValiation = [];

//
// 各LEDピクセルの目標値
var _pixelsTarget = [];


/*
	コンストラクタ
	pixelTotalNum 		[int]: 使用するLEDの全体個数。
	pixelCamNum 		[int]: カメラ照明に使うピクセル個数。LEDテープの後ろから数えた個数がカメラ照明用にあたる。
	setIntervalDelay 	[int]: 親クラスで定義するsetIntervalの第二引数delayをセット。
*/
var NeopixelLED = function(pixelTotalNum, pixelCamNum, setIntervalDelay)
{
	if (pixelTotalNum != null) _ledTotalNum = pixelTotalNum;
	if (pixelCamNum != null)
	{
		_ledCamNum = pixelCamNum;
		_ledDirectionNum = _ledTotalNum - _ledCamNum;
	}
	if (setIntervalDelay != null) _setIntervalDelay = setIntervalDelay;

	//
	// LED情報格納用配列を初期化
	for ( var i = 0, len = _ledTotalNum; i < len; ++i )
	{
		//
		// RGBで保持
		_pixelsInfo[i] = [0, 0, 0];
		_pixelsValiation[i] = [0, 0, 0];
		_pixelsTarget[i] = [0, 0, 0];
	}
};


/*
	処理
	※ setInterval()にセットしてください。推奨:30fps
*/
NeopixelLED.prototype.clock = function()
{
	//
	// 処理
	pixelsProcessClock();
};


/*
	ピクセルフレーム処理
*/
function pixelsProcessClock()
{
	//
	// 各LEDピクセルを回って処理
	for ( var i = 0, len = _ledTotalNum; i < len; ++i )
	{
		//
		// 変化量を加算
		for ( var j = 0, len_j = 3; j < len_j; ++j )
			_pixelsInfo[i][j] += _pixelsValiation[i][j];

		//
		// LEDに反映
		client.setPixel( i, _pixelsInfo[i][0], _pixelsInfo[i][1], _pixelsInfo[i][2] );
		client.writePixels();

		//
		// 加算後の値をみて目標値に達していれば処理を止める(変化量を0に)
		if ( _pixelsValiation[i][0] > 0 )
		{
			if ( _pixelsInfo[i][0] > _pixelsTarget[i][0] )
				_pixelsValiation[i] = [0, 0, 0];
		}
		else
		{
			if ( _pixelsInfo[i][0] < _pixelsTarget[i][0] )
				_pixelsValiation[i] = [0, 0, 0];
		}
	}
}


/**********************************************
 * 演出用LED制御点灯
 rgbR		[int]	: [0 - 255]赤
 rgbG		[int]	: [0 - 255]緑
 rgbB		[int]	: [0 - 255]青
 brightness [float]	: [0.0 - 1.0]輝度
 duration 	[float]	: [0.0 - ]継続時間[sec]
 **********************************************/
NeopixelLED.prototype.controlDirection = function( rgbR, rgbG, rgbB, brightness, duration )
{	
	// 最大値、最小値を求める
	var max = Math.max(Math.max(rgbR, rgbG), rgbB);
	var min = Math.min(Math.min(rgbR, rgbG), rgbB);

	// 色相を求める
	var hue = 0;
	if (max == min) hue = 0;
	else if (max == rgbR) hue = 60 * (rgbG - rgbB) / (max - min) + 0;
	else if (max == rgbG) hue = (60 * (rgbB - rgbR) / (max - min)) + 120;
	else hue = (60 * (rgbR - rgbG) / (max - min)) + 240;

	// 彩度を求める
	var saturation = 0;
	saturation = max - min;
	saturation = saturation / 255;
	
	//
	// HSV情報からRGB情報に変換し、変化量と目標値を設定
	for ( var i = 0, len = _ledDirectionNum; i < len; ++i )
		this.controlPixel( i, hue, saturation, brightness, duration );
};


/**********************************************
 * カメラ照明用LED制御
 rgbR		[int]	: [0 - 255]赤
 rgbG		[int]	: [0 - 255]緑
 rgbB		[int]	: [0 - 255]青
 brightness [float]	: [0.0 - 1.0]輝度
 duration 	[float]	: [0.0 - ]継続時間[sec]
 **********************************************/
NeopixelLED.prototype.controlCamLight = function( rgbR, rgbG, rgbB, brightness, duration )
{
	//
	// 最大値、最小値を求める
	var max = Math.max(Math.max(rgbR, rgbG), rgbB);
	var min = Math.min(Math.min(rgbR, rgbG), rgbB);

	//
	// 色相を求める
	var hue = 0;
	if (max == min) hue = 0;
	else if (max == rgbR) hue = 60 * (rgbG - rgbB) / (max - min) + 0;
	else if (max == rgbG) hue = (60 * (rgbB - rgbR) / (max - min)) + 120;
	else hue = (60 * (rgbR - rgbG) / (max - min)) + 240;

	//
	// 彩度を求める
	var saturation = 0;
	saturation = max - min;
	saturation = saturation / 255;

	//
	// HSVで処理
	this.controlCamLightHsv( hue, saturation, brightness, duration );
};


/**********************************************
 * カメラ照明用LED制御[HSV指定]
 hue 		[int]	: 色相 0 - 360
 saturation	[float]	: 彩度 0.0 - 1.0
 brightness	[float]	: 明度 0.0 - 1.0
 duration 	[float]	: 移行時間(sec) 0.0 -
 **********************************************/
NeopixelLED.prototype.controlCamLightHsv = function( hue, saturation, brightness, duration )
{
	//
	// カメラ照明用LED制御
	for ( var i = 0, len = _ledCamNum; i < len; ++i )
	{
		this.controlPixel( _ledDirectionNum + i, hue, saturation, brightness, duration );
	}
};


/**********************************************
 * カメラ照明用LEDを個別制御[RGB指定]
 n 			[int]	: 対象のLEDの通し番号(4つなら0,1,2,3) 0 -
 rgbR		[int]	: [0 - 255]赤
 rgbG		[int]	: [0 - 255]緑
 rgbB		[int]	: [0 - 255]青
 brightness [float]	: [0.0 - 1.0]輝度
 duration 	[float]	: [0.0 - ]継続時間[sec]
 **********************************************/
NeopixelLED.prototype.controlCamPixelRgb = function( n, rgbR, rgbG, rgbB, brightness, duration )
{
	//
	// 最大値、最小値を求める
	var max = Math.max(Math.max(rgbR, rgbG), rgbB);
	var min = Math.min(Math.min(rgbR, rgbG), rgbB);

	//
	// 色相を求める
	var hue = 0;
	if (max == min) hue = 0;
	else if (max == rgbR) hue = 60 * (rgbG - rgbB) / (max - min) + 0;
	else if (max == rgbG) hue = (60 * (rgbB - rgbR) / (max - min)) + 120;
	else hue = (60 * (rgbR - rgbG) / (max - min)) + 240;

	//
	// 彩度を求める
	var saturation = 0;
	saturation = max - min;
	saturation = saturation / 255;

	//
	// ピクセル操作
	this.controlPixel( _ledDirectionNum + n, hue, saturation, brightness, duration );
};


/**********************************************
 * カメラ照明用LEDを個別制御[HSV指定]
 n 			[int]	: 対象のLEDの通し番号(4つなら0,1,2,3) 0 -
 hue 		[int]	: 色相 0 - 360
 saturation	[float]	: 彩度 0.0 - 1.0
 brightness	[float]	: 明度 0.0 - 1.0
 duration 	[float]	: 移行時間(sec) 0.0 -
 **********************************************/
NeopixelLED.prototype.controlCamPixelHsv = function( n, hue, saturation, brightness, duration )
{
	//
	// ピクセル操作
	this.controlPixel( _ledDirectionNum + n, hue, saturation, brightness, duration );
};


/**********************************************
 * LEDピクセルを個別制御[HSV指定]
 n 			[int]	: 対象のLEDの通し番号 0 -
 hue 		[int]	: 色相 0 - 360
 saturation	[float]	: 彩度 0.0 - 1.0
 brightness	[float]	: 明度 0.0 - 1.0
 duration 	[float]	: 移行時間(sec) 0.0 -
 **********************************************/
NeopixelLED.prototype.controlPixel = function( n, hue, saturation, brightness, duration )
{
	//
	// HSV情報からRGB情報に変換し、変化量と目標値を設定
	var msduration = duration * 1000;
	var diff = 0;
	for ( var i = 0, len = 3; i < len; ++i )
	{
		//
		// 目標値を設定
		_pixelsTarget[n][i] = OPC.hsv(hue/360, saturation, brightness)[i];

		//
		// 差分を算出
		diff = _pixelsTarget[n][i] - _pixelsInfo[n][i];
		
		//
		// 変化量を設定
		// 1フレーム分の変化量を算出
		// 移行時間が0なら差分がそのまま変化量
		if ( msduration != 0 )
			_pixelsValiation[n][i] = diff / msduration * _setIntervalDelay;
		else
			_pixelsValiation[n][i] = diff;
	}
};


/**********************************************
 * 全開点灯
 **********************************************/
NeopixelLED.prototype.full = function()
{
	for ( var i = 0, len = _ledDirectionNum; i < len; ++i )
		this.controlDirection( 255, 255, 255, 1.0, 0.11 );
};


/**********************************************
 * 演出用LED消灯
 **********************************************/
NeopixelLED.prototype.off = function()
{
	for ( var i = 0, len = _ledDirectionNum; i < len; ++i )
		this.controlPixel( i, 0, 0, 0, 0.1 );
};


/**********************************************
 * すべてのLED消灯
 **********************************************/
NeopixelLED.prototype.allOff = function()
{
	for ( var i = 0, len = _ledTotalNum; i < len; ++i )
		this.controlPixel( i, 0, 0, 0, 0.1 );
};


module.exports = NeopixelLED;
                
              
/_node/camera.js
カメラ制御用JS
                /**
 * カメラ制御用JS
 */
var _process	= require("child_process");
var _exec		= _process.exec;
var _spawn		= _process.spawn;
var _cvApp = null;
var _running = false;
var _scanCompleteHandler = null;

// スキャンフラグ
var _scanFlag = false;
var _scanWithDebugFlag = false;

// 設定更新フラグ
var _updateConfigFlag = false;

// 終了フラグ
var _quitFlag = false;



/**
 * カメラ設定更新用のコマンドを叩く
 * @param propName	カメラの設定用プロパティ名
 * @param value		数値
 */
function _setCameraConf( propName, value )
{
	var command = "v4l2-ctl --set-ctrl=" + propName + "=" + value;
	console.log( "_setCameraConf => " + command );
	var c = _exec( command, function( err, stdout, stderr )
	{
		if( err ) {
			console.log("_setCameraConf:Error!" + err.message );
		}
	});
}


/**
 * カメラを初期化
 * ※オートホワイトバランス・オートフォーカスなどを切る
 */
module.exports.init = function()
{
	_setCameraConf( "white_balance_temperature_auto", 0 );
    _setCameraConf( "focus_auto", 0 );
    _setCameraConf( "exposure_auto", 1 );
    _setCameraConf( "exposure_auto_priority", 0 );
};


/**
 * カメラを起動する
 */
module.exports.start = function()
{
	if( _running )
	{
		console.log( "camera is already running!");
		return "FAILED";
	}

	console.log( "start camera...");
	_running = true;

	// アプリ起動
	var appCommand = "./app";
	var cwd = { cwd:"/home/root/anyscan/_opencv" };

	_cvApp = _spawn( appCommand, [], cwd );

	// std::outの検知時に実行
	_cvApp.stdout.on( "data", function( data )
	{
		var str = String( data );

		if( str.search("input_cin") != -1 )
		{
			if( _quitFlag )
			{
				_cvApp.stdin.write( "quit\n");
				_quitFlag = false;
				_running = false;

			}
			else if( _updateConfigFlag )
			{
				console.log("updateConfig...");

				_cvApp.stdin.write( "updateConfig\n");
				_updateConfigFlag = false;
			}
			else if( _scanWithDebugFlag )
			{
				console.log("scan with debug...");

				_cvApp.stdin.write( "scan_debug\n");
				_scanWithDebugFlag = false;
			}
			else if( _scanFlag )
			{
				console.log("scan...");

				_cvApp.stdin.write( "scan\n");
				_scanFlag = false;
			}
			else
			{
				_cvApp.stdin.write( "ignore\n");
			}
		}
		else if(  str.search("captured") != -1 )
		{
			if( _scanCompleteHandler != null )
			{
				console.log( "Scan Complete!" );
				_scanCompleteHandler();
				_scanCompleteHandler = null;
			}
		}
	});


	// Close時
	_cvApp.on( "close", function( code )
	{
		console.log( "Camera App is CLOSE: [code->]" + code );
		_running = false;
		_cvApp = null;
	});

	return "SUCCESS";
};


/**
 * カメラ撮影
 */
module.exports.scan = function( onComplete )
{
	if( !_running )
	{
		console.log( "camera is NOT running!" );
		return "FAILED";
	}

	console.log( "START SCAN please wait..." );
	_scanCompleteHandler = onComplete;

	_scanFlag = true;
};


/**
 * カメラ撮影
 */
module.exports.scanWithDebug = function( onComplete )
{
	if( !_running )
	{
		console.log( "camera is NOT running!" );
		return "FAILED";
	}

	console.log( "START SCAN with Debug please wait..." );
	_scanCompleteHandler = onComplete;

	_scanWithDebugFlag = true;
};





/**
 * カメラ情報の更新フラグを立てる
 */
module.exports.updateConfig = function()
{
	if( !_running )
	{
		console.log( "camera is NOT running!" );
		return;
	}

	console.log( "UPDATE CONFIG please wait..." );
	_updateConfigFlag = true;
};


/**
 * カメラ撮影終了
 */
module.exports.stop = function()
{
	if( !_running )
	{
		console.log( "camera is NOT running!" );
		return "FAILED";
	}

	console.log( "CLOSE CAMERA please wait..." );
	_quitFlag = true;

	return "SUCCESS";
};


/**
 * カメラが起動中かどうかを取得
 * @returns {boolean}
 */
module.exports.isRunning = function(){
	return _running;
};
                
              

※Webカメラの明度調整部分などのコードは、今回使用したLogicoolのカメラ専用に設定項目名や値の範囲を合わせているので、Webカメラの型番が違うとトラブルが生じる可能性がある。

/_node/julius.js
Julius制御用JS
                /**
 * Julius制御用JS
 */
var _process = require("child_process");
// var _exec	= _process.exec;
var _spawn		= _process.spawn;
var _running = false;
var _julius = null;
var _onDetectionHandler = null;


/**
 * Julius制御開始
 * @param onUpdate
 */
module.exports.start = function( cardID, subDeviceID, micLv, rejectShort, dictElem, onDetection )
{
	if( _running )
	{
		console.log("Julius is already running!");
		return "FAILED";
	}

	console.log("start Julius...");
	_running = true;
	_onDetectionHandler = onDetection;


	// Juliusのアプリを実行
	var juliusCommand = "./app";
	var cwd = { cwd:"/home/root/anyscan/_julius" };

	_julius = _spawn(
		juliusCommand
		,[
			 "-C" , "./julius.jconf"
			,"-lv" , String(micLv)
			,"-rejectshort" , String(rejectShort)
			,"-gram" , "./dict/commands_" + String( dictElem )
		 ]
		 ,cwd
	);

	// std::cout認識時に実行
	_julius.stdout.on( "data", function( data )
	{
		// 語句認識
		var str = String( data );

		if( str.search( "words" ) != -1 ) {
			if( _onDetectionHandler != null )
			{
				_onDetectionHandler(data);
			}
		}
	});

	// stdエラー時
	_julius.stderr.on( "data", function( data )
	{
		console.log( "[JULIUS]stdErr:" + data );
	});

	// 実行エラー時
	_julius.on( "error", function( err )
	{
		console.log( "[JULIUS]error:" + err );
	});

	// Close時
	_julius.on( "close", function( code )
	{
		console.log( "Julius is CLOSE: [code->]" + code );
	});

	return "SUCCESS";
};


/**
 * Juliusの制御停止
 * @param onDetection
 */
module.exports.stop = function()
{
	if( !_running ) {
		console.log( "Julius is already stopped." );
		return "FAILED";
	}

	_julius.kill('SIGINT');
	_running = false;
	_onDetectionHandler = null;

	return "SUCCESS";
};
                
              
commands_fire.dict
ファイル(ボイス音声認識辞書ファイル例)
                0	[JUMP]	j a N p u
0	[JUMP]	j a: N p u
0	[TOBE]	t o b e
0	[TOBE]	t o b e:
0	[TOBE]	t o b e e
0	[TOBE]	t o b e q e
0	[TOBUNDA]	t o b u N d a
0	[DASH]	d a sh u
0	[DASH]	d a q sh u
0	[HASHIRE]	h a sh i r e
0	[HASHIRE]	h a sh i r e:
0	[YOKERO]	y o k e r o
0	[KAWASE]	k a w a s e
0	[NIGERO]	n i g e r o
0	[MAMORE]	m a m o r e
0	[FUSEGE]	f u s e g e
0	[GUARD]	g a: d o
0	[GUARD]	g a d o:
0	[BARIYA]	b a r i a
0	[BARIYA]	b a r i a:
0	[BARIYA]	b a r i y a
0	[BARIYA]	b a r i y a:
1	[PUNCH]	p a N ch i
1	[PUNCH]	p a N ch i d a
1	[PUNCH]	p a: N ch i
1	[PUNCH]	p a: N
1	[NAGURE]	n a g u r e
1	[NAGURE]	n a g u r u N
1	[TATAKE]	t a t a k e
1	[TATAKE]	t a t a k u
1	[TATAKE]	t a t a k u N
1	[KICK]	k i q k u
1	[KICK]	k i q k u d a
1	[KICK]	k i k u
1	[KICK]	k i: k u
1	[KERE]	k e r e
1	[KERE]	k e r e:
1	[KERE]	k e r u
1	[KERE]	k e r u N
1	[KERITOBASE]	k e r i t o b a s e
2	[ANDO]	a N d o
2	[SOSITE]	s o sh i t e
2	[KARANONO]	k a r a n o
3	[SHIRO]	sh i r o
3	[SHITEKUDASAI]	sh i t e k u d a s a i
3	[DESU]	d e s u
4	[MAGIC_A]	f a i y a b o: r u
4	[MAGIC_A]	f a i y a: b o: r u
4	[MAGIC_A]	f a i y a: b o:
4	[MAGIC_A]	f a i a b o: r u
4	[MAGIC_A]	f a i a: b o: r u
4	[MAGIC_A]	f a i a: b o:
4	[MAGIC_A]	a b o: r u
4	[MAGIC_A]	a b o u r u
4	[MAGIC_A]	b o: r u
4	[MAGIC_A]	b o u r u
4	[MAGIC_B]	f u r e i m u b a: s u t o
4	[MAGIC_B]	f u r e i m u b a s u t o
4	[MAGIC_B]	f u r e i m u b u r a s u t o
4	[MAGIC_B]	f u r e: m u b a: s u t o
4	[MAGIC_B]	f u r e: m u b a s u t o
4	[MAGIC_B]	f u r e: m u b u r a s u t o
4	[MAGIC_B]	r e: m u b a: s u t o
4	[MAGIC_B]	r e: m u b a s u t o
4	[MAGIC_B]	b a: s u t o
4	[MAGIC_B]	b a s u t o
4	[MAGIC_B3]	n a g i h a r a e
4	[MAGIC_C]	b a: n i N g u s u t o r a i k u
4	[MAGIC_C]	b a: n i N g u t o r a i k u
4	[MAGIC_C]	b a: n i N s u t o r a i k u
4	[MAGIC_C]	b a: n i N t o r a i k u
4	[MAGIC_C]	s u t o r a i k u
5	[IKE]	i k e
5	[IKE]	i k e:
5	[IKE]	i k q e
5	[SOKODA]	s o k o d a
5	[FUKITOBE]	f u k i t o b e
5	[KURAE]	k u r a e
5	[KURAE]	k u r a e:
5	[HISSATSU]	h i q s a ts u
5	[HISSATSU]	h i s a ts u
5	[SUPER]	s u: p a:
5	[HYPER]	h a i p a:
5	[ULTRA]	u r u t o r a
5	[CHOU]	ch o:
5	[CHOU]	ch o u
6	[SYOUKAN]	sh o: k a N
6	[SYOUKAN]	sh o u k a N
6	[SYOUKAN]	s o u k a N
6	[SYOUKAN]	i d e y o
7	[silB]	silB
8	[silE]	silE
9	[sp]	sp
                
              

「子どもたちに、自分が描いたモンスターを操るという、リアルな魔法使い体験を楽しんでもらいたいですね」(宗)
「大人の人も子どもに戻って、『こんなことができるんだ!』と驚いてくれたらいいなと思います」(山田)
「楽しい体験を通して『自分も作る人になりたい』と思ってもらいたい。次の世代の心に残っていくものを作りたいです」(長洞)
(※いずれもこれまでの『Artist Showcase』密着記事より)
彼らが培ってきた独自技術と、Intel® Edisonというテクノロジーが融合して、誰もが魔法の世界を体験できる時代がやってきた!
「いや、まだまだです。必ずや、もっとよいものに進化させてみせます!」
その意気やよし。岡山発、“マジカルな未来” 行き。ココノヱの今後の展開から、目が離せない…

8月1日、MFT 2015内 Intel『Artist Showcase』会場にて

ARTIST PROFILE

ココノヱ

Web制作会社として岡山で創業。社名の由来は “もうひとかさね” を社訓に、重ねて重ねて重ねてよいものを作り続ける姿勢から。「dotFes 2010」で発表した『撃墜王ゲーム』をきっかけにインタラクティブな作品群で注目を集め、イベント向けのインスタレーション制作や、自社ブランドのゲームなどを展開している。

http://9ye.jp/about