手乗り戦極ドライバーをつくる

今回はコンパクトサイズで本格的な変身ギミックを楽しめる『手乗り戦極ドライバー』のご紹介です。

製作経緯

前作『逢魔降臨歴ワンダーライド電子ブック』で『セイバー』関係の作品作りはやりきった感があるのと、ぼちぼち次のライダーの情報が出てくる時期ですので、それまでの短い期間でできそうなところということで、前から時間があったらやろうと思っていたアイデアを消化することにしました。

『鎧武』についてはこれまで扱ったことがなかったので、CSM戦極ドライバーも買ってしまったことですし、機会があれば何か作りたいなと思っていました。ただ、『鎧武』のコレクトアイテムである『ロックシード』については、正直本家バンダイ様が頑張り過ぎで、特に何か創作を加えなくても良いぐらいに豊富にバリエーションが存在するので、自分としては特に作りたいモノがあるわけではありませんでした。そんな折、唐突に食玩『HYPER DETAIL GEAR KAMEN RIDER』のシリーズ第三弾が発売され、丁度『鎧武祭り』のタイミングと重なってか『戦極ドライバー』がラインナップされていたので、これに発光&音声ギミックを入れてみるのも良いかも、ということで確保して置いていたのでした。

実は食玩『HYPER DETAIL GEAR KAMEN RIDER』を使った作品はこれが初めてではなく、自分にとって初のライダー玩具の改造作品である『コンプリートセレクション風食玩アークル』も、このシリーズをベースに作成したものでした。もう5年ぐらい前ですね。原点回帰みたいなもので、なかなか感慨深いものがあります。

 

特徴

「コンパクト&シンプルな戦極ドライバー」以外の何者でも無い、という感じです。音声&発光の内容はオレンジロックシードに絞っていますが、「ロックオン」→「ソイヤ」→「オレンジアームズ 花道オンステージ」の変身シーケンスと、必殺技三種(オレンジスカッシュ/オーレ/スパーキング)に対応しています。

食玩としては同じタイミングでバロン版も出ていたので、バナナロックシードとの交換もできなくはなかったのですが、それをやろうとするとロックシードの個別認識やフェイスプレート部分の鎧武からバロンへの換装など、実装がどんどん複雑になってしまい、そうなるともはや普通のDX版で遊んでる方が楽しいしラク、となってしまいますので、今回はシンプルにオレンジロックシードに絞ることにしました。

 

最初の「ロックオン」操作については、食玩上は一応錠の部分を手で引っ張たり押したりすれば解錠/施錠はできるようには作られているのですが、操作としては細かくてやりづらいため、錠の裏にボタンを用意して、擬似的にはなりますが簡単に「ロックオン」をできるようにしました。

 

ブレードの操作については、食玩仕様そのままでは全部自分で動かさないといけないのですが、内側にバネを仕込んで、DX版のような跳ね上げ(?)ギミックの再現に拘りました。中身については後のハードウェア解説の中で説明します。また、ロックシードの展開も、同じく食玩仕様そのままでは全部手で動かす必要があったのですが、こちらもブレード操作に連動して展開できるように色々調節しています。こちらも詳しくはハードウェア解説にて。

あと、電源には単四電池×3本を使用しています。リチウムイオンポリマー充電池を使えばもっと小型化もできましたが、今回はちょっと実際の玩具っぽい感じにしてみたくて、取り扱い&入手が容易な単四電池で動くようにしてみました。

 

ハードウェア解説

まず、今回使用した具材は以下です。まず、何はともあれ食玩『HYPER DETAIL GEAR KAMEN RIDER 3 05 戦極ドライバー 鎧武ver.』です。

販売は終了していますが、店舗によってはまだ在庫が残っているかもしれません。あと、基本割高ですがフリマアプリで手に入れることも可能かもしれません。

 

続いてメイン基板です。今回は無線通信とかややこしいことはしなくて良いので、Seeeduino XIAOを使用しています。

最近はシンプルな工作はSeeeduino XIAO、ややこしい工作はATOM Lite、というのが自分の中でのよくあるパターンです。

 

MP3プレイヤーは、再生する音声が少ないので、MP3ボイスモジュールを使用しています。

MP3プレイヤーについては、大容量が必要なとき、あるいは少しでも小型化が必要なときにはDF Playerを使用する、というのがよくある使い分けです。

 

スピーカーは、いつもはこれを使うのですが、今回はもう少しコンパクトにしたかったので、こちらを使用しました。

 

LEDは、ギアトリンガーを発光改造させる際にまとめ買いした黄色LEDを使用しています。

 

それから錠のスイッチ用に、こちらのディテクタスイッチを使用しています。

 

ブレード操作の検出をどうするかは凄く悩んだのですが、最終的に導電テープを接触させる方法にしました。

詳細は後ほど説明します。

 

電池ボックスはどこで手に入れたか忘れましたが、多分こんなやつだと思います。

ちゃんと昇圧回路とか作れば電池2本にしてもっとコンパクトにできそうな気もするのですが、その辺はちょっと不勉強なのと、ちょっとでもシンプルな方が作るのはラクなので、素直に単四電池3本にしています。

 

あとは適当にピンソケット・ヘッダ、抵抗、M2&M3ネジ、配線用コード、といったところです。あと大事な部品として、ブレード跳ね上げ用のバネがあるのですが、こちらはこれまでに分解した玩具の残パーツの中から適当なものを使いました。それから、オレンジ部分の展開ギミック用に、小型のネオジム磁石も2個ほど使っています。

また、大部分の部品を収めることになる台座については、適当なケースを買ってきて加工する手もありますが、先日せっかく3Dプリンタを購入したので、丁度良いサイズ・中身のものを自分で作ることにしました。

設計はこんな感じです。

実際の玩具を意識して、電池ボックスには底面の蓋を開けてアクセスできる仕様にしてみました。

矢印っぽい溝はなくても全然良いのですが、ある方が断然それっぽい(?)気がしたのでつけてみました。

 

で、実際に出力して、簡単な表面処理(ヤスリがけ→サーフェイサー→マットブラック塗装)をしたものがこちらになります。

結構綺麗にできました。

 

さて、ここからは中身の詳細に入ります、

基本的な構成は過去に作ったアークルと同様です。ベルト部分に詰め込む部品は最小限とし、ほとんどの部品は台座側に寄せます。回路図で表すとこんな感じです。

 

ブレードの跳ね上げギミックをどう実現するか、またブレード操作をどう認識させるかでだいぶ試行錯誤したのですが、

 

最終的にこの形に落ち着きました。スペースと仕組みの都合上、ディテクタスイッチでブレード操作を検知させるのが難しかったので、導電テープ同士の接触で検知させることにしました。また、バネに電気の導線の役割を兼ねさせることで、中の配線をすっきりさせて玩具としての耐久性を上げています。

 

続いてオレンジ部分の展開ですが、基本的にはブレード操作時に自重で開くように、可動部をヤスリで削って緩くしています。

ただ、それだけだとブレード操作してないときでもパカパカ開いてしまうので、

ロックシードの本体部分にネオジム磁石を仕込んで、

開く側の方にも薄型のネオジム磁石を入れることで、緩くロックするようにしています。ちなみに開く側に単にネオジム磁石を貼り付けるだけだと段差が出来て後でシールを綺麗に貼れないので、段差を埋めるためのパーツを3Dプリンタで作っています。このレベルなら一瞬でモデリング&出力ができるので、やはり3Dプリンタは大変便利です。

 

あと発光部のオレンジの断面ですが、実は食玩付属のシールでは厚みがあり過ぎて発光が綺麗に透過しなかったので、わざわざDX版のオレンジロックシードを入手してシールを剥がし、それを縮小コピーして貼り直しています。

こういうところで結構コストがかかっていたりします。

 

ベルト側の方はこんな感じで、続いて台座側です。こちらはそんなに特筆すべきところはなく、内部の実装はこんな感じになっています。

 

一点あるとすれば、先にも触れた通り、市販の玩具っぽいものにするために

「裏側のフタを開けて電池交換できるようにする」というところには拘りました。

台座は専用設計なので、単四電池ボックスはピッタリはまり、逆さで電池を入れても落下することはありません。

先にも述べた通り、昇圧回路をちゃんと組めば単四電池2本で済むと思います。さらに言えば、私が普段作品作りに多用しているリチウムイオンポリマー充電池を使えば、確実にもっとコンパクトにできます。ただ、リチウムイオンポリマー充電池は安全性や扱いやすさの観点で基本的にはあまり使いたくないものなので、実際の玩具を意識して、今回は意図的に単四電池を電源に採用しました。

 

ソフトウェア解説

最後にソフト側の解説です。今回の設計図(状態遷移図)はこんな感じでとてもシンプルです。

発光のパターンをプログラムするのと、時間経過による状態遷移の管理が若干面倒なぐらいです。ソースコードの全文は本記事の最後に掲載しておりますので、必要な方はそちらをご参照ください。

実は最初にプログラムを組んだときは、玩具の仕様に従って「ロックオン!」のときには発光させていなかったのですが、動画撮影が終わった翌日に長男と一緒『鎧武』の本編の変身シーンをよく見てみると、「ロックオン!」のときに発光してる…ということに気付いてしまいました。玩具の方ではおそらく仕様上、ロックシード側で「ロックオン!」の操作を検知できない為にオミットされているのかと思いますが、今回作っているものはドライバーとロックシードが一体化している構造なので、やろうと思えばできてしまう…ということで、プログラムを組み直して再撮影しました。

また、鳴らす音声についてですが、CSMはお金がなくてプロジェクト・アーク版を買ってしまったので、戦極ドライバーはあってもオレンジロックシードがありませんでした。

ということで、戦極ドライバー側の音声はCSM版から録音していますが、

オレンジロックシードの音声はDX版から録音しています。その後、音声編集ソフトで音声を合成してMP3ファイルを作成し、MP3プレイヤーで再生させるようにしています。

 

ちなみに、動画で写っているCSM戦極ドライバーにセットされている鎧武のフェイスプレートですが、これも実はDX版です。

左がDX版、右がCSM版のフェイスプレートです。DX版フェイスプレートはそのままではCSM版戦極ドライバーにはセットできませんが、幸いなことにCSM版フェイスプレートと部品の構成とネジ穴の位置は完全に一致しています。

そのため、

DX版鎧武フェイスプレートの部品の一部を少しカットした後、

その鎧武フェイスプレートをプロジェクト・アーク版に付属する黒影トルーパーフェイスプレートの部品に挟み込んでしまえば、和風待機音を鳴らせてかつ、見た目は鎧武のCSM対応フェイスプレートが出来上がります。

 

まあ、見た目だけなので、キャラクターボイスは城乃内パティシエのままなのですが。キャラクターボイスなしで遊ぶか、あるいは彼が鎧武に変身した世界線として遊んでもらえればと思います。

 

まとめ

以上、手乗り戦極ドライバーのご紹介でした。

特撮玩具は毎年買い続けているとどうしても収納場所が問題になってきて、最終的に箱にしまって物置の奥底に収納せざるを得なくなる、ということがあると思います。そうなってしまうと、せっかく手元にあるのに遊べない(=出すのに手間がかかる)となってしまうので、こんな感じでコンパクト、でも気軽にしっかり遊べる、こういう玩具展開があっても良いのかなと思いました。バンダイ様が本気でこういうのを量産すれば、多分電源はボタン電池にできるし、そうすると台座ももっとコンパクトにできるので、1個2,000円ぐらいのシリーズとして展開できなかいな…と思ったりします。それぐらいなら自分なら買うかな、多分。

何か良いアイデアが思い付けばロックシードの改造もやってみたいと思いますが、現状はアイデアがないのと、他にもやりたいことや、そろそろ次のライダーの登場も控えているので、とりあえずは次はそれら優先でやっていきたいと思います。

 

ソースコード

以下、ソースコードの全文です。発光パターンのところがちょっと複雑かもしれませんが、そこを除いた全体構造はとてもシンプルです。

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

#define LOOP_DELAY_MS 20

#define MP3_RX_PIN   0
#define MP3_TX_PIN   1
#define LED_PIN      2
#define LOCK_SW_PIN  3
#define BLADE_SW_PIN 4

#define ON  LOW
#define OFF HIGH

uint8_t lock_sw  = OFF;
uint8_t blade_sw = OFF;
uint8_t prev_lock_sw  = OFF;
uint8_t prev_blade_sw = OFF;

#define STATE_INIT   0
#define STATE_READY  1
#define STATE_CHANGE 2
#define STATE_SP_A   3
#define STATE_SP_B   4
#define STATE_SP_C1  5
#define STATE_SP_C2  6

uint8_t state      = STATE_INIT;
uint8_t prev_state = STATE_INIT;

#define RESET_INIT_MS   35000
#define RESET_CHANGE_MS  1000
unsigned long reset_start_time = 0;

void print_state(){
  if(prev_state != state){
    switch(state){
    case STATE_INIT:   Serial.println(F("STATE_INIT"));   break;
    case STATE_READY:  Serial.println(F("STATE_READY"));  break;
    case STATE_CHANGE: Serial.println(F("STATE_CHANGE")); break;
    case STATE_SP_A:   Serial.println(F("STATE_SP_A"));   break;
    case STATE_SP_B:   Serial.println(F("STATE_SP_B"));   break;
    case STATE_SP_C1:  Serial.println(F("STATE_SP_C1"));  break;
    case STATE_SP_C2:  Serial.println(F("STATE_SP_C2"));  break;
    default: ;
    }
  }
}

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

#include <SoftwareSerial.h>

SoftwareSerial ss_mp3_player(MP3_RX_PIN, MP3_TX_PIN);

#define SOUND_VOLUME_DEFAULT 22 // 0〜30

#define SOUND_POWER_ON 1
#define SOUND_LOCK_ON  2
#define SOUND_CHANGE   3
#define SOUND_SQUASH   4
#define SOUND_AU_LAIT  5
#define SOUND_SPARKING 6

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

  // Macの場合は、MP3データを全て"ZH"フォルダにコピーした後、
  // ターミナルでZHフォルダに入り、"$ rm ._*"のコマンドで、
  // "._01.mp3"のような名前で裏で勝手に生成されているファイル
  // ("$ ls -a"のコマンドでないと見えない、AppleDouble Header file)
  // を削除しないと、挙動がおかしくなるので注意

  uint8_t play[6] = {0xAA,0x07,0x02,0x00,track,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(){
  if(prev_state != state){
    switch(state){
    case STATE_INIT:
      break;
    case STATE_READY:
      mp3_play(SOUND_LOCK_ON);
      break;
    case STATE_CHANGE:
      if(prev_state == STATE_READY){
        mp3_play(SOUND_CHANGE);
      }
      break;
    case STATE_SP_A:
      mp3_play(SOUND_SQUASH);
      break;
    case STATE_SP_B:
      mp3_play(SOUND_AU_LAIT);
      break;
    case STATE_SP_C1:
      mp3_play(SOUND_SPARKING);
      break;
    case STATE_SP_C2:
      mp3_play(SOUND_SPARKING);
      break;
    default:
      ;
    }
  }
}

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

#define LED_BRIGHTNESS 255 // 255が上限値

unsigned long led_pattern_start_time = 0;
unsigned long prev_blink_time        = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
boolean is_inc = true;

int  prev_interval_ms = 0;
uint8_t prev_steps = 0;

void led_base_pattern_on(){
  analogWrite(LED_PIN, LED_BRIGHTNESS);
  prev_interval_ms = 0;
  prev_steps = 0;
}

void led_base_pattern_off(){
  analogWrite(LED_PIN, 0);
  prev_interval_ms = 0;
  prev_steps = 0;
}

void led_base_pattern_blink(unsigned long now_ms, int interval_ms){
  if(now_ms - prev_blink_time >= interval_ms){
    if(is_lighting){
      analogWrite(LED_PIN, 0);
    }else{
      analogWrite(LED_PIN, LED_BRIGHTNESS);
    }
    is_lighting = !is_lighting;
    prev_blink_time = now_ms;
  }

  prev_interval_ms = interval_ms;
  prev_steps = 0;
}

void led_base_pattern_inc(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(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 l_step = LED_BRIGHTNESS/steps;
  analogWrite(LED_PIN, l_step*current_step);

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_dim(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(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 l_step = LED_BRIGHTNESS/steps;
  analogWrite(LED_PIN, l_step*(steps-current_step));

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_blink_slowly(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
    is_inc = true;
  }
  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 l_step = LED_BRIGHTNESS/steps;
  if(is_inc){
    analogWrite(LED_PIN, l_step*current_step);
  }else{
    analogWrite(LED_PIN, l_step*(steps-current_step));
  }
  if(now_ms - inc_dim_start_time >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_time = now_ms;
  }

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_pattern_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 1200){                          led_base_pattern_dim(now_ms,1200, 20);}
  else if(1200 < passed_ms && passed_ms <= 1800){ led_base_pattern_dim(now_ms, 600, 10);}
  else if(1800 < passed_ms && passed_ms <= 2800){ led_base_pattern_dim(now_ms,1000, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_change(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_inc(now_ms, 100, 5);}
  else if( 100 < passed_ms && passed_ms <=  300){ led_base_pattern_on();}
  else if( 300 < passed_ms && passed_ms <=  700){ led_base_pattern_dim(now_ms, 400, 10);}
  else if( 700 < passed_ms && passed_ms <= 1600){ led_base_pattern_off();}
  else if(1600 < passed_ms && passed_ms <= 1800){ led_base_pattern_inc(now_ms, 200, 10);}
  else if(1800 < passed_ms && passed_ms <= 2100){ led_base_pattern_dim(now_ms, 300, 10);}
  else if(2100 < passed_ms && passed_ms <= 2400){ led_base_pattern_on();}                 // オレンジ
  else if(2400 < passed_ms && passed_ms <= 3000){ led_base_pattern_dim(now_ms, 600, 10);}
  else if(3000 < passed_ms && passed_ms <= 3300){ led_base_pattern_on();}                 // アームズ
  else if(3300 < passed_ms && passed_ms <= 3900){ led_base_pattern_dim(now_ms, 600, 10);}
  else if(3900 < passed_ms && passed_ms <= 4300){ led_base_pattern_on();}                 // 花道
  else if(4300 < passed_ms && passed_ms <= 5000){ led_base_pattern_dim(now_ms, 700, 10);}
  else if(5000 < passed_ms && passed_ms <= 5300){ led_base_pattern_on();}                 // オン
  else if(5300 < passed_ms && passed_ms <= 5800){ led_base_pattern_dim(now_ms, 500, 10);}
  else if(5800 < passed_ms && passed_ms <= 6300){ led_base_pattern_on();}                 // ステージ
  else if(6300 < passed_ms && passed_ms <= 6850){ led_base_pattern_dim(now_ms, 550, 10);}
  else if(6850 < passed_ms && passed_ms <= 7300){ led_base_pattern_on();}
  else if(7300 < passed_ms && passed_ms <= 8100){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_sp_A(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_inc(now_ms, 100, 5);}
  else if( 100 < passed_ms && passed_ms <=  300){ led_base_pattern_on();}
  else if( 300 < passed_ms && passed_ms <=  700){ led_base_pattern_dim(now_ms, 400, 10);}
  else if( 700 < passed_ms && passed_ms <= 1600){ led_base_pattern_off();}
  else if(1600 < passed_ms && passed_ms <= 1800){ led_base_pattern_on();}  // オレンジ
  else if(1800 < passed_ms && passed_ms <= 2300){ led_base_pattern_dim(now_ms, 500, 10);}
  else if(2300 < passed_ms && passed_ms <= 2800){ led_base_pattern_on();}  // スカッシュ
  else if(2800 < passed_ms && passed_ms <= 3300){ led_base_pattern_dim(now_ms, 500, 10);}
  else if(3300 < passed_ms && passed_ms <= 3500){ led_base_pattern_off();}
  else if(3500 < passed_ms && passed_ms <= 4200){ led_base_pattern_inc(now_ms, 700, 20);}
  else if(4200 < passed_ms && passed_ms <= 4300){ led_base_pattern_off();}
  else if(4300 < passed_ms && passed_ms <= 4700){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(4700 < passed_ms && passed_ms <= 5500){ led_base_pattern_inc(now_ms, 800, 20);}
  else if(5500 < passed_ms && passed_ms <= 6000){ led_base_pattern_on();}
  else if(6000 < passed_ms && passed_ms <= 6700){ led_base_pattern_dim(now_ms, 700, 20);}
  else if(6700 < passed_ms && passed_ms <= 6900){ led_base_pattern_inc(now_ms, 200, 10);}
  else if(6900 < passed_ms && passed_ms <= 7300){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(7300 < passed_ms && passed_ms <= 7800){ led_base_pattern_inc(now_ms, 500, 10);}
  else if(7800 < passed_ms && passed_ms <= 8300){ led_base_pattern_on();}
  else if(8300 < passed_ms && passed_ms <= 9300){ led_base_pattern_dim(now_ms,1000, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_sp_B(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_inc(now_ms, 100, 5);}
  else if( 100 < passed_ms && passed_ms <=  300){ led_base_pattern_on();}
  else if( 300 < passed_ms && passed_ms <=  700){ led_base_pattern_dim(now_ms, 400, 10);}
  else if( 700 < passed_ms && passed_ms <= 1600){ led_base_pattern_off();}
  else if(1600 < passed_ms && passed_ms <= 1800){ led_base_pattern_on();}  // オレンジ
  else if(1800 < passed_ms && passed_ms <= 2200){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(2200 < passed_ms && passed_ms <= 2750){ led_base_pattern_dim(now_ms, 550, 10);} // オー
  else if(2750 < passed_ms && passed_ms <= 3500){ led_base_pattern_dim(now_ms, 750, 10);} // レ
  else if(3500 < passed_ms && passed_ms <= 3700){ led_base_pattern_off();}
  else if(3700 < passed_ms && passed_ms <= 4200){ led_base_pattern_inc(now_ms, 500, 10);}
  else if(4200 < passed_ms && passed_ms <= 4500){ led_base_pattern_on();}
  else if(4500 < passed_ms && passed_ms <= 4900){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(4900 < passed_ms && passed_ms <= 5400){ led_base_pattern_inc(now_ms, 500, 10);}
  else if(5400 < passed_ms && passed_ms <= 5900){ led_base_pattern_on();}
  else if(5900 < passed_ms && passed_ms <= 6900){ led_base_pattern_dim(now_ms,1000, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_sp_C(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_inc(now_ms, 100, 5);}
  else if( 100 < passed_ms && passed_ms <=  300){ led_base_pattern_on();}
  else if( 300 < passed_ms && passed_ms <=  700){ led_base_pattern_dim(now_ms, 400, 10);}
  else if( 700 < passed_ms && passed_ms <= 1600){ led_base_pattern_off();}
  else if(1600 < passed_ms && passed_ms <= 1800){ led_base_pattern_on();}  // オレンジ
  else if(1800 < passed_ms && passed_ms <= 2200){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(2200 < passed_ms && passed_ms <= 2500){ led_base_pattern_dim(now_ms, 300, 10);} // スパー
  else if(2500 < passed_ms && passed_ms <= 3500){ led_base_pattern_dim(now_ms,1000, 20);} // キング
  else if(3500 < passed_ms && passed_ms <= 4000){ led_base_pattern_inc(now_ms, 500, 10);}
  else if(4000 < passed_ms && passed_ms <= 4500){ led_base_pattern_on();}
  else if(4500 < passed_ms && passed_ms <= 5100){ led_base_pattern_dim(now_ms, 600, 20);}
  else if(5100 < passed_ms && passed_ms <= 5200){ led_base_pattern_on();}
  else if(5200 < passed_ms && passed_ms <= 5700){ led_base_pattern_dim(now_ms, 500, 10);}
  else if(5700 < passed_ms && passed_ms <= 6200){ led_base_pattern_on();}
  else if(6200 < passed_ms && passed_ms <= 7000){ led_base_pattern_dim(now_ms, 800, 20);}
  else if(7000 < passed_ms && passed_ms <= 7100){ led_base_pattern_on();}
  else if(7100 < passed_ms && passed_ms <= 7500){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(7500 < passed_ms && passed_ms <= 8500){ led_base_pattern_on();}
  else if(8500 < passed_ms && passed_ms <= 9500){ led_base_pattern_dim(now_ms,1000, 20);}
  else{                                           led_base_pattern_off();}
}

uint8_t sp_state = STATE_CHANGE;

void control_led(){

  unsigned long now_ms = millis();

  if(prev_state != state){
    // 状態遷移時には基本的に発光パターンをリセットさせるが、
    // SP_A/B/C1/C2からCHANGEに時間経過で戻るときはリセットさせない
    if(state == STATE_CHANGE){
      if(prev_state == STATE_READY){
        led_pattern_start_time = now_ms;
      }else{ // STATE_SP_A/B/C1/C2のときはリセットさせない
        ;
      }
      // 必殺技状態を記憶する
      sp_state = prev_state;
    }else{
      led_pattern_start_time = now_ms;
    }
  }

  unsigned long passed_ms = now_ms - led_pattern_start_time;

  switch(state){
  case STATE_INIT:
    led_base_pattern_off();
    break;
  case STATE_READY:
    led_pattern_ready(passed_ms, now_ms);
    break;
  case STATE_CHANGE:
    switch(sp_state){
    case STATE_SP_A:
      led_pattern_sp_A(passed_ms, now_ms);
      break;
    case STATE_SP_B:
      led_pattern_sp_B(passed_ms, now_ms);
      break;
    case STATE_SP_C1:
    case STATE_SP_C2:
      led_pattern_sp_C(passed_ms, now_ms);
      break;
    default:
      led_pattern_change(passed_ms, now_ms);
    }
    break;
  case STATE_SP_A:
    led_pattern_sp_A(passed_ms, now_ms);
    break;
  case STATE_SP_B:
    led_pattern_sp_B(passed_ms, now_ms);
    break;
  case STATE_SP_C1:
  case STATE_SP_C2:
    led_pattern_sp_C(passed_ms, now_ms);
    break;
  default:
    ;
  }
}

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

void setup(){
  Serial.begin(115200);
  pinMode(LED_PIN,      OUTPUT);
  pinMode(LOCK_SW_PIN,  INPUT_PULLUP);
  pinMode(BLADE_SW_PIN, INPUT_PULLUP);

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

  led_base_pattern_off();
}

void loop(){
  unsigned long now_ms = millis();

  //////////////////// 状態遷移管理(スイッチ) ////////////////////
  lock_sw  = digitalRead(LOCK_SW_PIN);
  blade_sw = digitalRead(BLADE_SW_PIN);

  switch(state){
  case STATE_INIT:
    if(prev_lock_sw == OFF && lock_sw == ON){
      state = STATE_READY;
      reset_start_time = now_ms;
    }
    break;
  case STATE_READY:
    if(prev_blade_sw == OFF && blade_sw == ON){
      state = STATE_CHANGE;
    }
    break;
  case STATE_CHANGE:
    if(prev_blade_sw == OFF && blade_sw == ON){
      state = STATE_SP_A;
      reset_start_time = now_ms;
    }else if(prev_lock_sw == OFF && lock_sw == ON){
      state = STATE_READY;
      reset_start_time = now_ms;
    }
    break;
  case STATE_SP_A:
    if(prev_blade_sw == OFF && blade_sw == ON){
      state = STATE_SP_B;
      reset_start_time = now_ms;
    }else if(prev_lock_sw == OFF && lock_sw == ON){
      state = STATE_READY;
      reset_start_time = now_ms;
    }
    break;
  case STATE_SP_B:
    if(prev_blade_sw == OFF && blade_sw == ON){
      state = STATE_SP_C1;
      reset_start_time = now_ms;
    }else if(prev_lock_sw == OFF && lock_sw == ON){
      state = STATE_READY;
      reset_start_time = now_ms;
    }
    break;
  case STATE_SP_C1:
    if(prev_blade_sw == OFF && blade_sw == ON){
      state = STATE_SP_C2;
      reset_start_time = now_ms;
    }else if(prev_lock_sw == OFF && lock_sw == ON){
      state = STATE_READY;
      reset_start_time = now_ms;
    }
    break;
  case STATE_SP_C2:
    if(prev_blade_sw == OFF && blade_sw == ON){
      state = STATE_SP_C1;
      reset_start_time = now_ms;
    }else if(prev_lock_sw == OFF && lock_sw == ON){
      state = STATE_READY;
      reset_start_time = now_ms;
    }
    break;
  default:
    ;
  }

  //////////////////// 状態遷移管理(時間経過) ////////////////////
  switch(state){
  case STATE_READY:
    if(now_ms - reset_start_time >= RESET_INIT_MS){
      state = STATE_INIT;
    }
    break;
  case STATE_SP_A:
  case STATE_SP_B:
  case STATE_SP_C1:
  case STATE_SP_C2:
    if(now_ms - reset_start_time >= RESET_CHANGE_MS){
      state = STATE_CHANGE;
    }
    break;
  default:
    ;
  }

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

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

  ////////// デバッグ処理 ////////////////////
  print_state();

  ////////// 処理状態の保持 //////////////////
  prev_lock_sw  = lock_sw;
  prev_blade_sw = blade_sw;
  prev_state    = state;

  delay(LOOP_DELAY_MS);
}