友情バーストゴースト眼魂をつくる

今回は自分としてはとても珍しく、映像作品に登場しながら未製品化のものを素直に作ってみました。

制作経緯

友情バーストゴースト眼魂については、他に製作されている方が既におられます。

こちらの方とか、

こちらの方とか。

こういう先駆者がいるものは普段は作らないようにしているのですが、ゴースト眼魂はこれまで扱ったことがなかったのと、また普段玩具改造でお世話になっている方々(←改造のイラストを描いてくれたり、撮影や音声収録用に足りない玩具を貸し出してくれる方々)からの依頼もあり、今回特別に作ってみることにしました

(※通常は自分が作りたいと思ったものを好きなように作るのがポリシーのため、制作依頼はお受けしておりません。ご了承ください)

特徴

友情バーストゴースト眼魂の製作としてはおそらく最後発なので、できるだけ他の人が実施していない要素を実現するようにしてみました。

まず、中心部のみですが、音声に連動して発光するようにしてみました。

 

また、単体遊びのときはDX闘魂ブーストゴースト眼魂と同様、炎の音が聞こえるのみで、変身音や必殺音は鳴らないようにしています。DX闘魂ブーストゴースト眼魂では中→小→大の3種類の音声が鳴りますが、こちらは個人的な好みで中→大の2種類にしています。

 

次にメガウルオウダーとの連動です。

劇中での友情バースト魂への変身シーケンスには「テンガン」の操作がありません。

玩具のメガウルオウダーは発売時期の関係で「ネクロム」「グリム」「サンゾウ」「その他」の認識しかできない(←2つのスイッチのON/OFF組み合わせで2の2乗で4通り)ので、後に友情バーストゴースト眼魂を玩具化することになった場合に玩具のメガウルオウダーと連動しても違和感が出ないよう、「テンガン」の操作なしでの変身シーケンスになったものと思われます。

そのため、メガウルオウダー側は特に改造する必要はありません。はじめ、この変身シーケンスの詳細を知らなかった時点では「最悪メガウルオウダー側も改造しないと完全再現はできないかも」と思っていたので、変身シーケンスがわかったときにはとても安心しました。

 

変身シーケンスの再現にあたり、今回は「眼魂の下部にメガウルオウダーへの装填認識スイッチをつける」という方策をとることにしました。

奥側に見えているのが装填認識スイッチです。このスイッチがONになると、眼魂側から装填音と待機音が鳴り、変身待機状態になります。OFFになると、同じく眼魂側から取り外しの音声が鳴るようになっています。

このような仕様にした理由ですが、まず前提として、先ほど「メガウルオウダーの認識はネクロム・グリム・サンゾウ・その他の4通り」と書きましたが、「『その他』のときのみ、メガウルオウダーから装填音・待機音・取り外し音が鳴らない」という特徴があります。それは当然と言えば当然で、「その他」のときにはメガウルオウダー側のスイッチは何も押されていないため、メガウルオウダー側からすれば、いつ眼魂が装填され、いつ取り外されたかを判別しようがないからです。

その上で、劇中の変身シーケンスを確認してみると、まず確実に装填音は鳴っていますが、待機音は確認できません。本当は鳴るのかもしれませんが、眼魂装填後のアラン様のボタン操作が早過ぎて待機音が鳴る前に次の待機音(炎の音)に入ってしまうため、鳴るのか鳴らないのかの詳細が不明です。

そんな状態ですが、友情バーストゴースト眼魂をメガウルオウダーに装填したときには、少なくとも装填音を鳴らさなくてはいけないことは確かです。しかし、元になっている闘魂ブーストゴースト眼魂は、メガウルオウダーからすると「その他」に分類されるため、装填音を鳴らせません。

対応方針は2つです。

  1. 友情バーストゴースト眼魂の下部に、ネクロム・グリム・サンゾウと同様の突起をつける
  2. 友情バーストゴースト眼魂に装填認識スイッチをつけて、眼魂側から装填音を鳴らす

1.の突起とは、以下のようなものです。眼魂の足にあたるところの根元らへんについています。

このような突起をつければ、メガウルオウダー側がネクロム・グリム・サンゾウのいずれかとして認識してくれるので、装填音や待機音、取り外し音をメガウルオウダーが鳴らしてくれるようになります。「テンガン」の操作をしなければ、ネクロムやグリム、サンゾウの固有音声が鳴ってしまうこともないので、それで特に問題はありません。

自分が2.を選んで1.を選ばなかった理由ですが、実は早とちりとも言える結果です。先ほど「劇中では装填音は確認できたが待機音は確認できなかった」と書きましたが、正確には「装填音は確認できたが、『イエッサー』と待機音は確認できなかった」が正しいです。この『イエッサー』とは、ネクロム・グリム・サンゾウを装填したときの装填音の後に流れる、言うなれば「認識音」とも言えるものです。劇中ではこの『イエッサー』が確認できなかったので、私は早とちりして、「装填音だけが鳴って、『イエッサー』は鳴らないようにしないといけないんだな」と思ってしまい、「それだと、1.のやり方だと『イエッサー』が鳴ってしまうからダメだな」と判断して、眼魂側から音声を鳴らす2.の方針で行くことにしました。これだと自分で好きなように音声を編集できるので。

ただ、先にも書きましたが、『イエッサー』と待機音が鳴らなかったのは、単に劇中でのアラン様のボタン操作が早過ぎて音声がキャンセルされてしまっただけで、「本来なら鳴るハズだった」という可能性もあります。そうであれば、1.でも特に問題ないことになります。このことに気づいたのは、もう作り終えてしまった後でした。

ちなみに、最終的に眼魂側からは装填音+待機音のみ鳴らすようにして、『イエッサー』は鳴らさないことにしました。これについては、依頼者の方とも相談した結果で、そもそも友情バーストゴースト眼魂が、メガウルオウダー側からすれば変身シーケンスが通常とは異なってしまうぐらいにイレギュラーな存在であったことを考えると、『イエッサー』も鳴らない可能性は充分にある、という判断です。もちろん、ネクロム・グリム・サンゾウと同様に『イエッサー』が鳴る可能性も充分にありますが、こればっかりは劇中では確認のしようがないので、最終的には好みの問題、ということで。

 

ちょっと想定外に『イエッサー』問題で文章が長くなってしまいましたが、話を戻します。

 

一度変身した後は連続して必殺技を出せるようにしています。ネクロムゴースト眼魂は「スタンバイ!」「ローディング!」「デストロイ!」を順に再生するだけなので、繰り返し遊んでいると眼魂側とメガウルオウダー側で音声がずれてしまいます。この問題は解消するようにしておきました。ちなみに、友情バースト魂の必殺技の音声は劇中から抽出するのが難しかったので、代わりにネクロムゴースト眼魂+メガウルオウダーの必殺技音声を使用しています。

なお、ゴーストドライバーに装填したときの設定は何も公開されていないと思われるので、対応していません。

外見について

ベースははもちろん、DX闘魂ブーストゴースト眼魂です。

塗装に使った塗料関係は以下だけです。

サーフェイサーは、後述する発光処理の関係で黒を使用しました。

塗装については初級者という自覚なのですが、今回初めてエアブラシを使ってみました。

入門用のガンダムマーカーエアブラシシステムですが、それでも自分にしては充分綺麗にできたかなと思います。

特に、正面の金色の輪が一番難しいと思っていたので、この部分が比較的綺麗にできただけでも大満足です。

ちなみにガンダムマーカーエアブラシシステム、モノとしては非常に良かったのですが、とにかく連続使用可能時間が短いというのが最大のネックに感じました。使用しているとエアー缶があっという間に冷えてしまって、すぐにエアーの圧が落ちて使えなくなってしまいます。幸いエアー缶を2本用意していたので途中で缶を切り換えることで連続作業時間を延ばしましたが、それでも缶が常温に戻るまでのインターバルを一度挟む必要がありました。一方で、ガンダムマーカーエアブラシシステムには電源の取り回しや面倒な後片付けが不要というとても強力なメリットもあるので、何とか充電式のコンプレッサーとかを繋いで使えないかなとか、今後色々模索していきたいと考えています。

あと、「エアブラシには興味はあるけど、そもそも塗装についてのちゃんとした知識がない」という方には以下の書籍がオススメです。

塗装の他、接着剤やヤスリの扱いについても詳しく説明されており、ライダー玩具を改造をする上でも役立つ知識が豊富に掲載されています。

 

さて、天面と正面のラベルについては、今回もいつもの方(←ZXライドウォッチプログライズライターのラベルを作成頂いた方)に描いてもらっています。

天面ラベルの方は、まずは以下のような透明ラベル用紙に印刷して、

ダイソーに売っていたキラキラホログラムチェーンに貼れば、

DXらしいホログラム仕様になります。あとはこの裏に両面テープを貼り付けて眼魂に貼り付ければOKです。

中身について

中には例によって、Arduino、フルカラーLED、MP3プレイヤー、バッテリーが入っています。使用した主な具材は以下です。

回路図はこんな感じです。

今回はスペースがかなりギリギリでした。

スペース的な問題は、MP3ボイスモジュールではなくDFPlayer Miniを使うと少し軽減されるかもしれません。MP3ボイスモジュールはコネクタ部分を取り外してもそこそこサイズが大きいので、余分なところはカットするなどして詰め込んでいます。

なお、メインの押しボタン(サイドスイッチ)については、元々闘魂ブーストに組み込まれていた基板をカットして使用しています。

こんな感じで。

 

難しかったのは発光処理です。

というのも、元の闘魂ブーストの内部パーツが赤のクリアの成型色なので、そのまま光らせてしまうと赤く光ってしまいます。

解決方針は素直に考えると2つです。

  1. 諦める
  2. 透明レジンでパーツを複製する

一つは、諦めること。発光させずとも、正面ラベルを以下のようなホログラム仕様にでもすれば、それはそれでかなりカッコ良いと思います。

ただ、自分が作るからにはそれはあまり積極的には選びたくありません。やっぱり光らせたい。

もう一つは、透明のレジンでパーツを複製すること。これがベストな気もするのですが、残念ながら自分はレジンを扱うとしばらく病院通いになってしまうため、この手は使えません。

そこで、3番目の手として、中心部のみを発光させることにしました。

中心部をこんな感じでくり抜いてしまい、中心部以外が赤く光って見えてしまわないように、内部パーツの表と裏を黒のサーフェイサーで塞いでしまいます(※写真撮り忘れました)。

炎の部分を金で塗装した後、

正面ラベルを透明ラベル用紙に印刷し、それを薄い白プラ板に貼り付けてから、両面テープで貼り付けます。

正面ラベルはホログラムにすると発光が綺麗に出なくなってしまうので、そのままの形で貼り付けるようにしました。

 

ちなみに起動時に一瞬緑色に光るのは、バグでなく、ネクロムゴースト眼魂から進化する様子を表すための意図的なものです。

 

最後に、Arduinoに書き込んだプログラムについては、記事の最後に全文掲載しておりますので、必要な方はご参照ください。眼魂の状態遷移はガシャット、ライドウォッチ、プログライズキーで採用されている3つのボタンの組み合わせによるものに比べれば遥かにシンプルで、状態遷移図を描き起こす必要もないぐらいです。

まとめ

ということで、友情バーストゴースト眼魂の作成でした。今回はほぼ劇中仕様通りにつくるということで、あまり自分のオリジナリティは出せませんでしたが、たまにはこういう制作もありかなと思いました。普通に楽しかったです。

ちなみに、友情バーストゴースト眼魂と同様に、「劇中に登場しながら未製品化のアイテム」として有名なところで、『仮面ライダービルド』の『キルバスパイダー』が存在しており、こちらの制作を期待される方もおられるのかなと思いますが、こちらについては制作の予定はありませんので、ご了承くださいませ。

 

最後に、ちょっと雑談です。

実は、リアルタイムで観ていたときは、自分の中での『仮面ライダーゴースト』の評価は、失礼ながらイマイチでした。なぜかと言われると言語化するのが難しいのですが…単に自分の好みに合わないというだけなのか…少なくとも、「毎週朝に楽しみに観る」という感じではないまま、一年間が終わってしまいました。

自分の中で評価が変わり始めたのは上記の小説を読んだところからです。これを読むと、ゴーストの世界観は実はかなり練られていたものの、それがテレビ本編ではあんまりうまく伝わっていなかったのかな…と思うようになりました。

そして今回の作業と並行しながら上記の書籍をパラパラと読んでいたのですが、ガンガンセイバーそのものの仕組みだったり、ゴーストガジェットシリーズとの連動など、玩具的にもよく練られているなと思うところもあり、上記の小説の内容もあって、自分の中では『ゴースト』は再評価されています。私に友情バーストゴースト眼魂の制作を依頼されるぐらい熱烈なファンがいるのも、今では納得です。

 

ソースコード

以下、ソースコードの全文です。必要な方だけどうぞ。

////////// 基本定義 ////////////////////////////////////////////////////////////

#define LOOP_DELAY_MS 20

#define MP3_RX_PIN    2
#define MP3_TX_PIN    3
#define SW_SIDE_PIN   4
#define SW_BOTTOM_PIN 5
#define LED_PIN       6

#define ON  LOW
#define OFF HIGH

#define STATE_SINGLE_A     0
#define STATE_SINGLE_B     1
#define STATE_INSERTED     2
#define STATE_CHANGE_READY 3
#define STATE_CHANGED      4
#define STATE_FINISH_READY 5
#define STATE_FINISH       6

uint8_t state      = STATE_SINGLE_A;
uint8_t prev_state = STATE_SINGLE_A;

uint8_t prev_sw_side_state   = OFF;
uint8_t sw_side_state        = OFF;
uint8_t prev_sw_bottom_state = OFF;
uint8_t sw_bottom_state      = OFF;

////////// 音声処理 /////////////////////////////////////////////////////////////

#include <SoftwareSerial.h>

#define SOUND_VOLUME_DEFAULT 25 // 0〜30

#define SOUND_POWER_ON  1
#define SOUND_SINGLE_S  2
#define SOUND_SINGLE_L  3
#define SOUND_INSERT    4
#define SOUND_READY     5
#define SOUND_CHANGE    6
#define SOUND_FINISH    7
#define SOUND_EJECT     8

#define WAIT_SOUND_MS 500

#define SOUND_WAITING_STATE_NONE   0
#define SOUND_WAITING_STATE_READY  1
#define SOUND_WAITING_STATE_CHANGE 2
#define SOUND_WAITING_STATE_FINISH 3

SoftwareSerial ss_mp3_player(MP3_RX_PIN, MP3_TX_PIN);

unsigned long sound_wait_start_time = 0;
uint8_t sound_waiting_state = SOUND_WAITING_STATE_NONE;

void mp3_play(uint8_t track){
  // この定義だと再生曲数は255に限定されるので注意。
  // Upper-Byte, Lower-Byteをちゃんと処理してやれば65535曲まで対応可能だが、
  // 容量的にそこまで使うことはほぼないと思われる。

  // なぜか、指定したトラックに対して以下のようにファイルが再生される。Macの環境依存?
  // 指定:1 -> 再生:01.mp3
  // 指定:2 -> 再生:不可
  // 指定:3 -> 再生:02.mp3
  // 指定:4 -> 再生:不可
  // 指定:5 -> 再生:03.mp3
  // 指定:6 -> 再生:不可
  // 指定:7 -> 再生:04.mp3
  // 指定:8 -> 再生:不可
  // ...
  // 原因は不明だが、とりあえず上記仕様に合わせるようにしておく

  uint8_t temp_track = track*2 - 1;
  uint8_t play[6] = {0xAA,0x07,0x02,0x00,temp_track,temp_track+0xB3};
  ss_mp3_player.write(play,6);
}

void mp3_stop(){
  unsigned char stop[4] = {0xAA,0x04,0x00,0xAE};
  ss_mp3_player.write(stop,4);
}

void mp3_set_volume(uint8_t vol){
  uint8_t volume[5] = {0xAA,0x13,0x01,vol,vol+0xBE};
  ss_mp3_player.write(volume,5);
}

void control_sound(unsigned long now_ms){
  if(prev_state != state){ // 状態遷移直後の処理
    switch(state){
    case STATE_SINGLE_A:
      if(prev_state == STATE_SINGLE_B){
        mp3_play(SOUND_SINGLE_L);
      }else{
        mp3_play(SOUND_EJECT);
      }
      break;
    case STATE_SINGLE_B:
      mp3_play(SOUND_SINGLE_S);
      break;
    case STATE_INSERTED:
      mp3_play(SOUND_INSERT);
      break;
    case STATE_CHANGE_READY:
      mp3_stop();
      sound_waiting_state = SOUND_WAITING_STATE_READY;
      sound_wait_start_time = now_ms;
      break;
    case STATE_CHANGED:
      mp3_stop();
      sound_waiting_state = SOUND_WAITING_STATE_CHANGE;
      sound_wait_start_time = now_ms;
      break;
    case STATE_FINISH_READY:
      mp3_stop();
      sound_waiting_state = SOUND_WAITING_STATE_READY;
      sound_wait_start_time = now_ms;
      break;
    case STATE_FINISH:
      mp3_stop();
      sound_waiting_state = SOUND_WAITING_STATE_FINISH;
      sound_wait_start_time = now_ms;
      break;
    default:
      ;
    }
  }else{ // 状態遷移後の時間経過処理
    switch(sound_waiting_state){
    case SOUND_WAITING_STATE_READY:
      if(now_ms - sound_wait_start_time >= WAIT_SOUND_MS){
        mp3_play(SOUND_READY);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_CHANGE:
      if(now_ms - sound_wait_start_time >= WAIT_SOUND_MS){
        mp3_play(SOUND_CHANGE);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_FINISH:
      if(now_ms - sound_wait_start_time >= WAIT_SOUND_MS){
        mp3_play(SOUND_FINISH);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    default:
      ;
    }
  }
}

////////// 発光処理 /////////////////////////////////////////////////////////////

#include <Adafruit_NeoPixel.h>
#define N_COLOR_LED 1

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_COLOR_LED, LED_PIN, NEO_GRB);

struct color_rgb {
  uint8_t r;
  uint8_t g;
  uint8_t b;
};

struct color_rgb COLOR_OFF        = {  0,  0,  0};
struct color_rgb COLOR_GREEN      = {  0,255,  0};
struct color_rgb COLOR_GREEN_HALF = {  0,128,  0};
struct color_rgb COLOR_GOLD       = {255,215,  0};
struct color_rgb COLOR_GOLD_HALF  = {128,108,  0};

struct color_rgb *color = &COLOR_OFF;

boolean is_led_active = false; // 電源投入直後はSTATE_SINGLE_AのときのLED発光を無効にする
unsigned long led_pattern_start_time = 0;
unsigned long inc_dim_start_time = 0;

uint8_t prev_r = 0;
uint8_t prev_g = 0;
uint8_t prev_b = 0;

struct color_rgb *prev_color = 0;
int  prev_interval_ms = 0;
uint8_t prev_steps = 0;

void update_color(uint8_t r, uint8_t g, uint8_t b){
  if(prev_r != r || prev_g != g || prev_b != b){
    pixels.setPixelColor(0, pixels.Color(r,g,b));
  }
  prev_r = r;
  prev_g = g;
  prev_b = b;
}

void led_base_pattern_on(struct color_rgb *color){
  update_color(color->r,color->g,color->b);
  prev_color       = 0;
  prev_interval_ms = 0;
  prev_steps       = 0;
}

void led_base_pattern_off(){
  update_color(0,0,0);
  prev_color       = 0;
  prev_interval_ms = 0;
  prev_steps       = 0;
}

void led_base_pattern_inc(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(color != prev_color || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  update_color(r_step*current_step, g_step*current_step, b_step*current_step);

  prev_color = color;
  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(color != prev_color || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  update_color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step));

  prev_color = color;
  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_pattern_init(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 1280){                          led_base_pattern_inc(&COLOR_GOLD_HALF, now_ms, 1280, 20);}
  else if(1280 < passed_ms && passed_ms <= 2320){ led_base_pattern_inc(&COLOR_GREEN_HALF, now_ms, 1040, 20);}
  else if(2320 < passed_ms && passed_ms <= 2880){ led_base_pattern_on(&COLOR_GOLD);}
  else if(2880 < passed_ms && passed_ms <= 4000){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 1120, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_single_s(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 380){                           led_base_pattern_inc(&COLOR_GOLD, now_ms, 380, 20);}
  else if( 380 < passed_ms && passed_ms <=  640){ led_base_pattern_on(&COLOR_GOLD);}
  else if( 640 < passed_ms && passed_ms <= 1100){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 690, 20);}
  else if(1100 < passed_ms && passed_ms <= 1500){ led_base_pattern_on(&COLOR_GOLD_HALF);}
  else if(1500 < passed_ms && passed_ms <= 2000){ led_base_pattern_dim(&COLOR_GOLD_HALF, now_ms, 500, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_single_l(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 80){                            led_base_pattern_on(&COLOR_GOLD);}
  else if(  80 < passed_ms && passed_ms <=  120){ led_base_pattern_off();}
  else if( 120 < passed_ms && passed_ms <=  360){ led_base_pattern_on(&COLOR_GOLD);}
  else if( 360 < passed_ms && passed_ms <=  880){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 1040, 20);}
  else if( 880 < passed_ms && passed_ms <= 1500){ led_base_pattern_on(&COLOR_GOLD);}
  else if(1500 < passed_ms && passed_ms <= 2200){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 700, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_inserted(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 920){                          led_base_pattern_on(&COLOR_GOLD);}
  else if(920 < passed_ms && passed_ms <= 1820){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 900, 20);}
  else{                                          led_base_pattern_off();}
}

#define READY_DURATION_MS 2290 // 待機中の1回の発光パターンに要する時間

void led_pattern_change_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= WAIT_SOUND_MS){
    led_base_pattern_off();
  }else if(WAIT_SOUND_MS < passed_ms && passed_ms < 21000){
    uint8_t ready_loop_count = (passed_ms - WAIT_SOUND_MS) / READY_DURATION_MS;
    unsigned long offset = ready_loop_count * READY_DURATION_MS;
    unsigned long base = WAIT_SOUND_MS + offset;
    if(base < passed_ms && passed_ms <= base+380){            led_base_pattern_inc(&COLOR_GOLD, now_ms, 380, 20);}     // 380
    else if(base+380  < passed_ms && passed_ms <= base+640){  led_base_pattern_on(&COLOR_GOLD);}                       // 260
    else if(base+640  < passed_ms && passed_ms <= base+1100){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 690, 20);}     // 460
    else if(base+1100 < passed_ms && passed_ms <= base+1500){ led_base_pattern_on(&COLOR_GOLD_HALF);}                  // 400
    else if(base+1500 < passed_ms && passed_ms <= base+2000){ led_base_pattern_dim(&COLOR_GOLD_HALF, now_ms,500, 20);} // 500
    else if(base+2000 < passed_ms && passed_ms <= base+READY_DURATION_MS){ led_base_pattern_off();}
  }else{
    led_base_pattern_off();
  }
}

void led_pattern_change(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= WAIT_SOUND_MS){                                        led_base_pattern_on(&COLOR_GOLD);}
  else if(WAIT_SOUND_MS < passed_ms && passed_ms <= WAIT_SOUND_MS+1000){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 1000, 20);}
  else{                                                                  led_base_pattern_off();}
}

void led_pattern_finish_ready(unsigned long passed_ms, unsigned long now_ms){
  // 現状は同じなので、そのまま呼び出す
  led_pattern_change_ready(passed_ms, now_ms);
}

void led_pattern_finish(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= WAIT_SOUND_MS){                                        led_base_pattern_on(&COLOR_GOLD);}
  else if(WAIT_SOUND_MS < passed_ms && passed_ms <= WAIT_SOUND_MS+1000){ led_base_pattern_dim(&COLOR_GOLD, now_ms, 1000, 20);}
  else{                                                                  led_base_pattern_off();}
}

void control_led(unsigned long now_ms){

  if(prev_state != state){
    if(prev_state != STATE_SINGLE_B && state == STATE_SINGLE_A){
      // STATE_SINGLE_B以外の状態からSTATE_SINGLE_Aに遷移するときは、LEDを発光させないようにする
      is_led_active = false;
    }else{
      // 通常の状態遷移では、LEDを有効にして発光パターンをリセットさせる
      is_led_active = true;
      led_pattern_start_time = now_ms;
    }
  }

  unsigned long passed_ms = now_ms - led_pattern_start_time;

  switch(state){
  case STATE_SINGLE_A:
    if(is_led_active){
      led_pattern_single_l(passed_ms, now_ms);
    }else{
      led_base_pattern_off();
    }
    break;
  case STATE_SINGLE_B:
    led_pattern_single_s(passed_ms, now_ms);
    break;
  case STATE_INSERTED:
    led_pattern_inserted(passed_ms, now_ms);
    break;
  case STATE_CHANGE_READY:
    led_pattern_change_ready(passed_ms, now_ms); // ここはOFFでも良いかも
    break;
  case STATE_CHANGED:
    led_pattern_change(passed_ms, now_ms);
    break;
  case STATE_FINISH_READY:
    led_pattern_finish_ready(passed_ms, now_ms); // ここはOFFでも良いかも
    break;
  case STATE_FINISH:
    led_pattern_finish(passed_ms, now_ms);
    break;
  default:
    ;
  }

  pixels.show();
}

////////// メイン処理 ////////////////////////////////////////////////////////////

void setup(){
  pinMode(SW_SIDE_PIN,  INPUT_PULLUP);
  pinMode(SW_BOTTOM_PIN,INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);

  // MP3プレイヤーセットアップ
  ss_mp3_player.begin(9600);
  mp3_set_volume(SOUND_VOLUME_DEFAULT);

  // 起動エフェクト
  mp3_play(SOUND_POWER_ON);
  led_pattern_start_time = millis();
  while(true){
    unsigned long now_ms = millis();
    unsigned long passed_ms =  now_ms - led_pattern_start_time;
    led_pattern_init(passed_ms, now_ms);
    pixels.show();
    if(passed_ms >= 3600){
      break;
    }
    delay(LOOP_DELAY_MS);
  }
}

void loop(){

  unsigned long now_ms = millis();

  //////////////////// ボタン処理(状態遷移管理) ////////////////////

  sw_side_state = digitalRead(SW_SIDE_PIN);
  if(prev_sw_side_state == OFF && sw_side_state == ON){
    switch(state){
    case STATE_SINGLE_A:
      state = STATE_SINGLE_B;
      break;
    case STATE_SINGLE_B:
      state = STATE_SINGLE_A;
      break;
    case STATE_INSERTED:
      state = STATE_CHANGE_READY;
      break;
    case STATE_CHANGE_READY:
      state = STATE_CHANGED;
      break;
    case STATE_CHANGED:
      state = STATE_FINISH_READY;
      break;
    case STATE_FINISH_READY:
      state = STATE_FINISH;
      break;
    case STATE_FINISH:
      state = STATE_FINISH_READY;
      break;
    default:
      ;
    }
  }

  sw_bottom_state = digitalRead(SW_BOTTOM_PIN);
  if(prev_sw_bottom_state == OFF && sw_bottom_state == ON){
    state = STATE_INSERTED;
  }else if(prev_sw_bottom_state == ON && sw_bottom_state == OFF){
    state = STATE_SINGLE_A;
  }

  ////////// 音声再生処理 ////////////////////
  control_sound(now_ms);

  ////////// LED発光処理 ////////////////////
  control_led(now_ms);

  ////////// 処理状態の保持 ////////////////////
  prev_sw_side_state = sw_side_state;
  prev_sw_bottom_state = sw_bottom_state;
  prev_state = state;

  delay(LOOP_DELAY_MS);
}