長嶋 洋一


遂に届いた。 ここ の11月15日、さらに ここ で書いていた、 これ である。

とりあえず、任意のムービーをLEDパターンで表示するためのファイル変換が ここ にあるらしい。


174個ものLEDを制御するには、単純にArduinoでは出来ないので、専用ハードを搭載していて、 その情報はまだ簡単には出てこない模様である。 とりあえず、 ここ の「」によれば、おいおい

  • Software to convert you're awesome videos to playback on the glasses
  • Software to control the glasses
が公開される、とあり、実際には前者のためのツールとして「ビデオをLEDパターンに変換するコンバータ」と、 「SDカード上のデータを再生するツール」について記載されているという。

さっそく、 ここ を見てみると、以下のように解説があった。


Place the input video file in the /data/ folder, named "". Input format is ideally 
627x220, 30 fps, MUST be .mov.
We also recommend using the Animation/Millions of Colors preset when exporting your .mov files.

Anything outside that size will be scaled, anything of a different FPS will be played at 30 FPS 
(unless SDplayer.ino, the player firmware for Bright Eyes, is modified).

The converter program itself is controlled via the keyboard:
- c converts the input file, closing the program once done
- p plays / pauses playback of the input file for review
- r resets playback of the movie to the start (this does not affect conversion)
- s shows / hides an overlay of the Bright Eyes LEDs for guidance
- t toggles text information display on or off

Output will be into the /data/ folder, with files named movieNNN.dat, sequentially numbered 
allowing you to convert lots of files and then transfer them to SD card for playback. When 
conversion finishes, the Processing console (black area at the bottom of the screen) will 
display the name of the most recently converted file.
また、 ここ を見てみると、以下のように解説があった(Macについてのみ)。

How to write your own firmware for the Bright Eyes board:

Just use the Arduino IDE! Instructions for transferring the code to the board (as we don't use 
USB uploading like a normal Arduino) are given below. First though, a few notes on what you 
CAN'T do with the Bright Eyes board.

The Bright Eyes bootloader uses EEPROM on the ATMEGA to store the size and last-modified 
date of the firmware file in order to avoid loading the same file at every boot. Using the same 
areas of EEPROM in your Arduino code will cause problems. The no-go areas of EEPROM are 
the last 8 bytes (as we store two DWORDS) - these are EEPROM addresses 0x3F8..0x3FF inclusive. 

If none of the above makes much sense to you, or you aren't sure if you are using EEPROM, 
don't worry - it's very difficult to do without realising so the chances are EXTREMELY good 
that you are fine! (And you won't break your Bright Eyes even if there is an issue, strange 
things will just happen..).

How to transfer firmware you have written in the Arduino IDE to the Bright Eyes board:

1. Enable verbose compilation output
With the Arduino IDE open, select Arduino -> Preferences from the Menu bar (Apple+,). 
Ensure the checkbox labelled "Show verbose output during: compilation" is checked.

2. Compile (Verify) your code
Click the Check (Verify) button as opposed to the usual Arrow (Upload) button. 
This will cause the code to be compiled, but not uploaded, and a lot of text will 
appear at the bottom of the Arduino IDE window (in the console area).

3. Copy the output directory location
When compilation is complete, there will be a penultimate line in the console output 
which shows the location of the compiled file. For example, it might say something like:


We need to copy just the folder, not the full file location, so select from the start of 
this line to the \ or / before the end and copy that text (Apple+C).

The clipboard will then contain something like: 


4. Find the compiled .hex file and copy to your SD card
We now need to find the compiled file:

Run Terminal - you can find this in Applications -> Utilities in Finder, or by typing 
"Terminal" into Spotlight (Apple+Space). Once terminal is open (another black screen!), 
type "open" then a space and then paste in the location we copied from earlier. For 
example, this might read:

open /var/folders/g7/btk0n8zs669b453qkgy94g980000gn/T/build2705930656204672901.tmp/

Once you have done this, press Enter and a new Finder window should open. You 
can close Terminal now if you like. This folder will contain a large number of files 
(all of which are related in some way to compiling your code). You are looking for 
the only file in this directory which has the extension .hex. It will be named after 
your sketch name, with .cpp.hex appended to the end. You will notice that this 
is the same as the ending of the penultimate line of compilation (the bit we didn't copy).

You can now copy this file to your SD card. Place it in the root of the card (that is, 
not in any folders, just on the card itself).

5. Rename the .hex file to firmware.hex
On the SD card, rename your copied file to firmware.hex. This will make sure that 
the Bright Eyes board knows to load it when you next start.

6. Flash the board!
With the Bright Eyes turned off, insert the SD card and then switch the board on. 
The red status LED should flash three times and then blink repeatedly during code 
flashing. Once it is done, leave the board for a while to reboot and your code 
should be running and ready to play with!

If for whatever reason, this doesn't work, try turning off the Bright Eyes board, 
reseating the SD card and turning the board on again.

It is also important to note that the Bright Eyes board stores the file size and 
last-modified date of the most recently flashed file. It uses this information to 
avoid flashing the same file at every boot, so if you are trying to flash the same 
file over itself (although you should NEVER need to do this), try recompiling the 
source to create a .hex file with a different last-modified date.
ムービーの画素はかなり変則的であるが、 ここ の「bright-eyes-coords.xml」を見ると、LEDに対応する座標は以下のようになっているようである。

address="1" leftX="63" rightX="68" topY="17" bottomY="29"
address="2" leftX="80" rightX="85" topY="17" bottomY="29"
address="3" leftX="97" rightX="102" topY="17" bottomY="29"
address="4" leftX="114" rightX="119" topY="17" bottomY="29"
address="5" leftX="131" rightX="136" topY="17" bottomY="29"
address="6" leftX="148" rightX="153" topY="17" bottomY="29"
address="7" leftX="165" rightX="170" topY="17" bottomY="29"
address="8" leftX="182" rightX="187" topY="17" bottomY="29"
address="9" leftX="199" rightX="204" topY="17" bottomY="29"
address="10" leftX="29" rightX="34" topY="46" bottomY="58"
address="11" leftX="46" rightX="51" topY="46" bottomY="58"
address="12" leftX="63" rightX="68" topY="46" bottomY="58"
address="13" leftX="80" rightX="85" topY="46" bottomY="58"
address="14" leftX="97" rightX="102" topY="46" bottomY="58"
address="15" leftX="114" rightX="119" topY="46" bottomY="58"
address="16" leftX="131" rightX="136" topY="46" bottomY="58"
address="17" leftX="148" rightX="153" topY="46" bottomY="58"
address="18" leftX="165" rightX="170" topY="46" bottomY="58"
address="19" leftX="182" rightX="187" topY="46" bottomY="58"
address="20" leftX="199" rightX="204" topY="46" bottomY="58"
address="21" leftX="216" rightX="221" topY="46" bottomY="58"
address="22" leftX="12" rightX="17" topY="75" bottomY="87"
address="23" leftX="29" rightX="34" topY="75" bottomY="87"
address="24" leftX="46" rightX="51" topY="75" bottomY="87"
address="25" leftX="63" rightX="68" topY="75" bottomY="87"
address="26" leftX="80" rightX="85" topY="75" bottomY="87"
address="27" leftX="97" rightX="102" topY="75" bottomY="87"
address="28" leftX="114" rightX="119" topY="75" bottomY="87"
address="29" leftX="131" rightX="136" topY="75" bottomY="87"
address="30" leftX="148" rightX="153" topY="75" bottomY="87"
address="31" leftX="165" rightX="170" topY="75" bottomY="87"
address="32" leftX="182" rightX="187" topY="75" bottomY="87"
address="33" leftX="199" rightX="204" topY="75" bottomY="87"
address="34" leftX="216" rightX="221" topY="75" bottomY="87"
address="35" leftX="233" rightX="238" topY="75" bottomY="87"
address="36" leftX="250" rightX="255" topY="75" bottomY="87"
address="37" leftX="12" rightX="17" topY="104" bottomY="116"
address="38" leftX="29" rightX="34" topY="104" bottomY="116"
address="39" leftX="46" rightX="51" topY="104" bottomY="116"
address="40" leftX="63" rightX="68" topY="104" bottomY="116"
address="41" leftX="80" rightX="85" topY="104" bottomY="116"
address="42" leftX="97" rightX="102" topY="104" bottomY="116"
address="43" leftX="114" rightX="119" topY="104" bottomY="116"
address="44" leftX="131" rightX="136" topY="104" bottomY="116"
address="45" leftX="148" rightX="153" topY="104" bottomY="116"
address="46" leftX="165" rightX="170" topY="104" bottomY="116"
address="47" leftX="182" rightX="187" topY="104" bottomY="116"
address="48" leftX="199" rightX="204" topY="104" bottomY="116"
address="49" leftX="216" rightX="221" topY="104" bottomY="116"
address="50" leftX="233" rightX="238" topY="104" bottomY="116"
address="51" leftX="250" rightX="255" topY="104" bottomY="116"
address="52" leftX="12" rightX="17" topY="133" bottomY="145"
address="53" leftX="29" rightX="34" topY="133" bottomY="145"
address="54" leftX="46" rightX="51" topY="133" bottomY="145"
address="55" leftX="63" rightX="68" topY="133" bottomY="145"
address="56" leftX="80" rightX="85" topY="133" bottomY="145"
address="57" leftX="97" rightX="102" topY="133" bottomY="145"
address="58" leftX="114" rightX="119" topY="133" bottomY="145"
address="59" leftX="131" rightX="136" topY="133" bottomY="145"
address="60" leftX="148" rightX="153" topY="133" bottomY="145"
address="61" leftX="165" rightX="170" topY="133" bottomY="145"
address="62" leftX="182" rightX="187" topY="133" bottomY="145"
address="63" leftX="199" rightX="204" topY="133" bottomY="145"
address="64" leftX="216" rightX="221" topY="133" bottomY="145"
address="65" leftX="233" rightX="238" topY="133" bottomY="145"
address="66" leftX="250" rightX="255" topY="133" bottomY="145"
address="67" leftX="29" rightX="34" topY="162" bottomY="174"
address="68" leftX="46" rightX="51" topY="162" bottomY="174"
address="69" leftX="63" rightX="68" topY="162" bottomY="174"
address="70" leftX="80" rightX="85" topY="162" bottomY="174"
address="71" leftX="97" rightX="102" topY="162" bottomY="174"
address="72" leftX="114" rightX="119" topY="162" bottomY="174"
address="73" leftX="131" rightX="136" topY="162" bottomY="174"
address="74" leftX="148" rightX="153" topY="162" bottomY="174"
address="75" leftX="165" rightX="170" topY="162" bottomY="174"
address="76" leftX="182" rightX="187" topY="162" bottomY="174"
address="77" leftX="199" rightX="204" topY="162" bottomY="174"
address="78" leftX="216" rightX="221" topY="162" bottomY="174"
address="79" leftX="63" rightX="68" topY="191" bottomY="203"
address="80" leftX="80" rightX="85" topY="191" bottomY="203"
address="81" leftX="97" rightX="102" topY="191" bottomY="203"
address="82" leftX="114" rightX="119" topY="191" bottomY="203"
address="83" leftX="131" rightX="136" topY="191" bottomY="203"
address="84" leftX="148" rightX="153" topY="191" bottomY="203"
address="85" leftX="165" rightX="170" topY="191" bottomY="203"
address="86" leftX="182" rightX="187" topY="191" bottomY="203"
address="87" leftX="199" rightX="204" topY="191" bottomY="203"
address="88" leftX="423" rightX="428" topY="17" bottomY="29"
address="89" leftX="440" rightX="445" topY="17" bottomY="29"
address="90" leftX="457" rightX="462" topY="17" bottomY="29"
address="91" leftX="474" rightX="479" topY="17" bottomY="29"
address="92" leftX="491" rightX="496" topY="17" bottomY="29"
address="93" leftX="508" rightX="513" topY="17" bottomY="29"
address="94" leftX="525" rightX="530" topY="17" bottomY="29"
address="95" leftX="542" rightX="547" topY="17" bottomY="29"
address="96" leftX="559" rightX="564" topY="17" bottomY="29"
address="97" leftX="406" rightX="411" topY="46" bottomY="58"
address="98" leftX="423" rightX="428" topY="46" bottomY="58"
address="99" leftX="440" rightX="445" topY="46" bottomY="58"
address="100" leftX="457" rightX="462" topY="46" bottomY="58"
address="101" leftX="474" rightX="479" topY="46" bottomY="58"
address="102" leftX="491" rightX="496" topY="46" bottomY="58"
address="103" leftX="508" rightX="513" topY="46" bottomY="58"
address="104" leftX="525" rightX="530" topY="46" bottomY="58"
address="105" leftX="542" rightX="547" topY="46" bottomY="58"
address="106" leftX="559" rightX="564" topY="46" bottomY="58"
address="107" leftX="576" rightX="581" topY="46" bottomY="58"
address="108" leftX="593" rightX="598" topY="46" bottomY="58"
address="109" leftX="372" rightX="377" topY="75" bottomY="87"
address="110" leftX="389" rightX="394" topY="75" bottomY="87"
address="111" leftX="406" rightX="411" topY="75" bottomY="87"
address="112" leftX="423" rightX="428" topY="75" bottomY="87"
address="113" leftX="440" rightX="445" topY="75" bottomY="87"
address="114" leftX="457" rightX="462" topY="75" bottomY="87"
address="115" leftX="474" rightX="479" topY="75" bottomY="87"
address="116" leftX="491" rightX="496" topY="75" bottomY="87"
address="117" leftX="508" rightX="513" topY="75" bottomY="87"
address="118" leftX="525" rightX="530" topY="75" bottomY="87"
address="119" leftX="542" rightX="547" topY="75" bottomY="87"
address="120" leftX="559" rightX="564" topY="75" bottomY="87"
address="121" leftX="576" rightX="581" topY="75" bottomY="87"
address="122" leftX="593" rightX="598" topY="75" bottomY="87"
address="123" leftX="610" rightX="615" topY="75" bottomY="87"
address="124" leftX="372" rightX="377" topY="104" bottomY="116"
address="125" leftX="389" rightX="394" topY="104" bottomY="116"
address="126" leftX="406" rightX="411" topY="104" bottomY="116"
address="127" leftX="423" rightX="428" topY="104" bottomY="116"
address="128" leftX="440" rightX="445" topY="104" bottomY="116"
address="129" leftX="457" rightX="462" topY="104" bottomY="116"
address="130" leftX="474" rightX="479" topY="104" bottomY="116"
address="131" leftX="491" rightX="496" topY="104" bottomY="116"
address="132" leftX="508" rightX="513" topY="104" bottomY="116"
address="133" leftX="525" rightX="530" topY="104" bottomY="116"
address="134" leftX="542" rightX="547" topY="104" bottomY="116"
address="135" leftX="559" rightX="564" topY="104" bottomY="116"
address="136" leftX="576" rightX="581" topY="104" bottomY="116"
address="137" leftX="593" rightX="598" topY="104" bottomY="116"
address="138" leftX="610" rightX="615" topY="104" bottomY="116"
address="139" leftX="372" rightX="377" topY="133" bottomY="145"
address="140" leftX="389" rightX="394" topY="133" bottomY="145"
address="141" leftX="406" rightX="411" topY="133" bottomY="145"
address="142" leftX="423" rightX="428" topY="133" bottomY="145"
address="143" leftX="440" rightX="445" topY="133" bottomY="145"
address="144" leftX="457" rightX="462" topY="133" bottomY="145"
address="145" leftX="474" rightX="479" topY="133" bottomY="145"
address="146" leftX="491" rightX="496" topY="133" bottomY="145"
address="147" leftX="508" rightX="513" topY="133" bottomY="145"
address="148" leftX="525" rightX="530" topY="133" bottomY="145"
address="149" leftX="542" rightX="547" topY="133" bottomY="145"
address="150" leftX="559" rightX="564" topY="133" bottomY="145"
address="151" leftX="576" rightX="581" topY="133" bottomY="145"
address="152" leftX="593" rightX="598" topY="133" bottomY="145"
address="153" leftX="610" rightX="615" topY="133" bottomY="145"
address="154" leftX="406" rightX="411" topY="162" bottomY="174"
address="155" leftX="423" rightX="428" topY="162" bottomY="174"
address="156" leftX="440" rightX="445" topY="162" bottomY="174"
address="157" leftX="457" rightX="462" topY="162" bottomY="174"
address="158" leftX="474" rightX="479" topY="162" bottomY="174"
address="159" leftX="491" rightX="496" topY="162" bottomY="174"
address="160" leftX="508" rightX="513" topY="162" bottomY="174"
address="161" leftX="525" rightX="530" topY="162" bottomY="174"
address="162" leftX="542" rightX="547" topY="162" bottomY="174"
address="163" leftX="559" rightX="564" topY="162" bottomY="174"
address="164" leftX="576" rightX="581" topY="162" bottomY="174"
address="165" leftX="593" rightX="598" topY="162" bottomY="174"
address="166" leftX="423" rightX="428" topY="191" bottomY="203"
address="167" leftX="440" rightX="445" topY="191" bottomY="203"
address="168" leftX="457" rightX="462" topY="191" bottomY="203"
address="169" leftX="474" rightX="479" topY="191" bottomY="203"
address="170" leftX="491" rightX="496" topY="191" bottomY="203"
address="171" leftX="508" rightX="513" topY="191" bottomY="203"
address="172" leftX="525" rightX="530" topY="191" bottomY="203"
address="173" leftX="542" rightX="547" topY="191" bottomY="203"
address="174" leftX="559" rightX="564" topY="191" bottomY="203"

outputSize width="627" height="220"

何か適当な動画をQuickTimePlayerPro7で指定された画素サイズに変換して、表示させるファイルを作るのは簡単そうだが、 なんせ これ は10個あるのに対して、こちらのBrightEyesは1個しかない。 たった1枚のSDカードを潰してしまって止まるのもナンなので、まずはとりあえず、 実験用のマイクロSDカードを業者に発注してみることにした。 送られて来たBrightEyesに付いていたマイクロSDカードは128MBだったが、 そんな小さな容量のものはかなり昔に製造中止であり、商社1社だけノーブランド品で「残り52個」と出ていたのは512MBだった。 2GBとかを越えるとフォーマットが違って来るので、とりあえずこれを注文した。

そして、Processingでのファイル変換というのをやってみる事にした。 あまり細かいものや、ミュージックビデオとか具象物や風景では、このBrightEyesの解像度では奇麗になる気がしない。 手元には過去に実験用に購入した多数の抽象的なループmovie素材があるので、 そこから これ を選んでみた。 そしてQuickTimePlayerPro7で、このムービーを

  • 画面サイズを 627x220 に
  • フレームレートを30 fps に
  • 圧縮を Animation に
  • 色は Millions に
  • 書き出しは""というファイル名
と指定して変換したのが である。 そしてProcessing2.0を呼び出して、スケッチフォルダ内に以下の「sketch_121217a.pde」を置いて、 さらにその中に「data」フォルダを置いて、input.movを置いた。
// storage class for each LED node
private class node{
  public int leftX;
  public int rightX;
  public int topY;
  public int bottomY; 
  node(int leftX, int rightX, int topY, int bottomY){
    this.leftX = leftX;
    this.rightX = rightX;
    this.topY = topY;
    this.bottomY = bottomY;

// movie
Movie movie;
boolean playing = false;
boolean converting = false;
float movFrameRate = 30;
int numFrames = 0;

// mapping
node[] nodes;
int mapWidth = 0;
int mapHeight = 0;
final int numLeds = 174;
final int headerLen = 16;
float dstW;
float dstH;
float srcW;
float srcH;

// output file saving
StringBuilder outputFileName = new StringBuilder("movie000.dat");
final int minMovieIndex = 0;
final int maxMovieIndex = 999;
final String header = "TWSU-BRIGHT-EYES";
int outputFileSize = 0;
byte[] outputArray;
int k;

// GUI
boolean showLeds = false;
boolean showText = true;

void setup() {
  // load the input movie and set it up
  movie = new Movie(this, dataPath(""));
  // create output array based on number of frames and header
  numFrames = getMovLength();
  outputFileSize = headerLen + (numFrames*numLeds);
  outputArray = new byte[outputFileSize];

  // load mapping XML 
  XML inputXML = loadXML("bright-eyes-coords.xml");
  XML[] coordsXML = inputXML.getChildren("node");
  XML sizeXML = inputXML.getChild("outputSize");
  // extract map width and height
  mapWidth = sizeXML.getInt("width");
  mapHeight = sizeXML.getInt("height");
  // set app size to match map
  size(mapWidth, mapHeight);
  // check we have a correct map
  if(coordsXML.length != numLeds){
    print("Input XML length is ");
    print(" should be ");
  } else {
    // if we do, create a node array
    nodes = new node[coordsXML.length];
    // populate the node array from the XML
    for (int i = 0; i < coordsXML.length; i++) {
      int address = coordsXML[i].getInt("address");
      int leftX = coordsXML[i].getInt("leftX");
      int rightX = coordsXML[i].getInt("rightX");
      int topY = coordsXML[i].getInt("topY");
      int bottomY = coordsXML[i].getInt("bottomY");
      nodes[address-1] = new node(leftX, rightX, topY, bottomY);

void draw() {
  // draw routine
  // draw the movie
  //image(movie, 0, 0, mapWidth, mapHeight);
  // draw the LEDs, if appropriate
  // create the text string, if appropriate
    String status = "";
      status = "Converting... " + (int)(100.0*movie.time()/movie.duration()) + "% complete"; 
    } else if(playing){
      status = "Playing... "  + (int)(100.0*movie.time()/movie.duration()) + "%";
    } else {
      status = "Press:\nc to convert\np to play / pause\nr to reset movie to the start\ns to overlay LEDs\nt to toggle text on / off";  
    // draw the text string
    fill(255, 0, 0);
    text(status, 10, 20);

void drawLeds(){
  // LED draw routine
  // set rectMode to match the way we store the LED co-ordinates

  // set stroke and fill (yellow, 75/255 opacity)
  fill(255, 255, 0, 75);

  // draw each rectangle, one by one
  for (int i = 0; i < nodes.length; i++) {
    rect(nodes[i].leftX, nodes[i].topY, nodes[i].rightX, nodes[i].bottomY);

void movieEvent(Movie m) {
  // Called every time a new frame is available to read
  // read the frame;
    // convert the frame if we need to
    // if we are at the end, finish conversion

void keyPressed(){
    case 'c':
      // convert the movie
        playing = false;
    case 'r':
      // reset the movie to the beginning
        playing = false;
    case 's':
      // toggle showing the leds
      showLeds = !showLeds;
    case 'p':
      // play / pause
        playing = !playing;
        } else {
    case 't':
      // toggle text visibility
      showText = !showText;

void convertFrame(){
  // a few variables for the pixel mapping
  float dstX;
  float dstY;
  float srcX;
  float srcY;
  float accum;
  float pixelCount;
  // one LED at a time  
  for(int j=0; j
エラーが出たのでさらにこの中にinput.movと並べて置いたのは、 ここ にあった「bright-eyes-coords.xml」である。 これで以下のように、BrightEyesのLED画素の配置されたウインドウ内で変換されたムービーをpreviewできた。

そして、変換開始の「c」コマンドを押すと、このプレビューウインドウが消えて、以下のように「begin conversion」、 そして「end conversion」、「movie000.dat」と簡潔で淡白なメッセージが出て(^_^;)、 無事にLEDが読み込む、つまりマイクロSDカードに書き込むべきデータに変換完了できた。 一度に1つしか出来ない、というのがこれで判明した。