オリジナルライドウォッチをつくる 〜ネオディケイド&ネオディエンドライドウォッチ〜

大変ご無沙汰しております、約四ヶ月ぶりの更新になります。過去最長の更新停止になってしまいました。。。

この期間、何もしていなかったのかというとそんなことはなく、平常運転で粛々とモノを作っておりました。なんでこんなに時間がかかってしまったかというと、もちろん本業や育児が忙しかったというのもあったのですが、何より今回題材に選んでしまったものの特性によるところが大きいです。

というわけで、お待たせ致しました。今回作った作品、『ネオディケイドライドウォッチ』『ネオディエンドライドウォッチ』のご紹介です。

作品内容

『ネオディケイドライドウォッチ』は、DX版ディケイドライドウォッチのシンプルな強化版という位置付けです。元々のDX版玩具でも主役ライダーのライドウォッチについては個別認識できていますが(※サブライダーは主役ライダーと同音声)、それでも名前の読み上げが変わる程度の変化でちょっと物足りない気がしましたので、以下の音声を強化致しました。

  • カメンライド(新規追加)
  • ファイナルアタックライド(新規追加)
  • ファイナルフォームライド(新規追加)
  • ファイナルフォームタイム(中間フォーム変身音声追加)
  • ファイナルアタックタイムブレイク(中間フォーム攻撃音追加)

ジクウドライバーに装填していない状態でライドウォッチを読み込ませるとカメンライド、そこから上部ボタン1回でファイナルアタックライド、さらにもう一回ボタンを押すとファイナルフォームライド、以後繰り返し、という動作になります。

ファイナルアタックライドは、カメンライドでそのライダーの姿に変身しているという想定なので、「ボルテックフィニッシュ!」などの必殺技名の読み上げ音声も入れています。一方、ファイナルアタックタイムブレイクは、あくまで『ディケイドアーマー ◯◯フォーム』という状態で発動する必殺技なので、「ファイナルアタックタイムブレイク!スパークリングフィニッシュ!」と必殺技名の読み上げが続くのは何だか冗長な気がして、意図的に「スパークリングフィニッシュ!」などの必殺技名は読み上げないようにしています。それでも可能な限り、「ファイナルアタックタイムブレイク!」の後の攻撃音は、玩具や映像の音声をベースに、各ライダーの中間フォームの固有のものにしています。なお、どうしても固有の音声データを作成できなかったライダーについては、元々のDX版の共通攻撃音を使用したり、あるいは自分で勝手にそれっぽい音声を合成したりしています(←ブレイドのライトニングスラッシュとか)。

あと地味な特徴として、上部ボタンが認識したライダーの色に変化する、というギミックも追加しています。ボタンがクリアブラック色なので、正面からだとちょっと見にくいですが。

 

そしてこちらが対になる存在、『ネオディエンドライドウォッチ』です。先に『ネオディケイドライドウォッチ』で紹介した音声強化を、サブライダー(2号、3号、敵ライダーなど)に対応させたものになります。再生可能な音声はネオディケイドライドウォッチと同様の5種類ですが、ディエンド仕様ということでカメンライド/ファイナルフォームタイムには召喚音が追加されていたり、ジオウではなくゲイツが使用する前提で、必殺技音声が『ファイナルアタックタイムブレイク』ではなく『ファイナルアタックタイムバースト』に代わっていたりします。

ベースはもちろんDX版ディケイドライドウォッチで、顔の部分を極力ディエンドに近づけるよう、ディケイドの顔下半分のバーコード部分のパーツを切り取ってしまって、それを上に持ってきて接着することで、頭のバーコード部分の高さの並び順を変化させています。そんな感じの簡単な工作なので、強い衝撃を与えると継ぎ足した部分がポロっと外れてしまうのが玉にキズですが、見た目にはそれっぽくできたかなと思っています。ちなみにバーコード部分に入っているレッド/シルバーのメタリックラインは、塗装ではなく百均で買ってきたテープを使って表現しています。

 

ネオディケイド/ネオディエンドで対応しているライドウォッチは以下になります。

No. ネオディケイド ネオディエンド
1 ジオウ
12 ゲイツ
24 ジオウ
32 クウガ
33 アギト G3
34 龍騎 ナイト
35 ファイズ カイザ
36 ブレイド ギャレン
37 響鬼 威吹鬼
38 カブト ガタック
39 電王 ゼロノス
40 キバ イクサ
41 デイケイド
42 ダブル アクセル
43 オーズ バース
44 フォーゼ メテオ
45 ウィザード ビースト
46 鎧武 バロン
47 ドライブ マッハ
48 ゴースト スペクター
49 エグゼイド
50 ビルド
51 クローズ
52 グリス
53 ローグ
54 ブレイブ
55 スナイプ
56 レーザー
74 ゲンム
76 エボル
93 ゲイツ
99 ディエンド
100 ビルド ジーニアス
101 エグゼイド ムテキ
102 ゴースト ムゲン
103 ドライブ トライドロン
104 鎧武 極
105 ウィザード インフィニティ
106 フォーゼ コズミック
107 オーズ プトティラ
108 ダブル エクストリーム
109 ディケイド コンプリート
110 キバ エンペラー
111 電王 ライナー
112 カブト ハイパー
113 響鬼 装甲
114 ブレイド キング
115 ファイズ ブラスター
116 龍騎 サバイブ
117 アギト シャイニング
118 クウガ アルティメット
128 おまけ1 おまけ2

開発経緯

今回は過去にない取り組みを2つ行なっています。一つは、2作品の同時並行作成。もう一つは、他の方とのコラボレーションです。これらを行うことになった経緯についてご説明します。

元々は素直に『全ライダーに対応したネオディケイドライドウォッチをつくる』という構想で開発をスタートさせました。この構想段階で兄と話をした際に、「サブライダーはディエンド(の持分)では?」とのコメントがあり、「それもそうだ」と思いましたので、大変ではありますが2つに分離して製作することに致しました。わざわざ分けて製作したことにはもう一つの狙いがあったのですが、それについては最後に改めて触れたいと思います。

さて、ネオディエンドライドウォッチを製作することを決めたのは良いのですが、一つ大きな問題がありました。ディケイドライドウォッチを改造するだけなら特に塗装は必要なかったのですが、ディエンド版を作るとなると、彼のベースカラーであるシアン色での全面塗装がほぼ必須になります。いつも塗装する時は、なんとなくそれっぽい色のスプレー缶を適当に買ってきて吹っかけていたのですが、今回はそれをやってしまうと「何かコレじゃない」モノになってしまう恐れが非常に高いと感じました。シアンは原色なので、「なんとなくそれっぽい」で誤魔化すのはおそらく困難だろうと思ったのです。

そこで、以前から玩具の塗装界隈で精力的に活動されていて、カッコ良い塗装作品を量産されていた#りんちゃんねるのK様にご相談させて頂いたところ、ディケイドの方と併せてディエンドの塗装を引き受けてくださることになりました。以前、万丈ライドウォッチを作成した際に、「クローズの目の部分を白くしたラベルを作る」という作業をひかり様にお願いしたことはありましたが、自分の作品の結構大きな部分を完全に他の方にお任せするというのは、今回が初めてです。

これまでは「全部自己完結・自己責任なので、最悪失敗しても良いや」と軽い気持ちで作品を作ってきましたが、他の人を巻き込んでしまった以上は、失敗はもちろん、適当なものを作ってしまうわけにもいきません。

そしてこちらの期待以上に、K様は塗装をカッコ良く仕上げてくださいました。塗装後に私が中身を作り込んでいく形で作業を進めていたので、正直中々のプレッシャーでした。

ハードウェア解説

ここではハードウェア構成について、簡単にですがご説明します。まずはいつものごとく、使用した具材の紹介からです。これまでの作品と全く変わっていません。

あとは適当に電源ON/OFF用のスライドスイッチや、配線用のコードなどです。スピーカーや上部ボタン、顔部分の白色LED、それからライドウォッチの配列ピンの認識用のスイッチは、DX版ディケイドライドウォッチに初めから組み込まれているものをそのまま流用します。なお、ハードウェア構成はネオディケイドもネオディエンドも全く同一なので、当然必要な具材も同じです。

回路図としては、こんな感じになります。

そして実際に配線したものが、こんな感じになります。相変わらずのいい加減さです。

今回少し気を配ったのは、メンテナンス性の良さを確保することです。

このネオディケイド/ネオディエンドライドウォッチは、その特性上、対応するライダーを後から編集したり追加したりするケースが出てくることは容易に想像がつきましたので、完成後もプログラムの書き換えや音声の追加が容易に行えるよう、本来ボタン電池がセットされていたスペースから必要部位に全てアクセスできるように部品を配置しました。リチウムイオンポリマー充電池への充電もここから行えるようにしています。

Arduinoのプログラムの書き換えは、ちょっと見辛いですが、ここにソケットを設けているので、ここから行うことができます。

ソフトウェア解説

ソースコードのベースになっているのは、万丈ライドウォッチのときに紹介したソースコードです。これに、

  • ライドウォッチの配列ピンの読み取り機能の追加
  • 「ライドウォッチ認識」という新たなイベントが発生することによる、状態遷移の修正

を行なったものが、今回のソースコードになります。

ライドウォッチの配列ピンの読み取り機能のプログラムを書くためには、当然ライドウォッチの配列ピンのルール(=配列ピンからの、個別の識別番号の計算方法)を知る必要がありますが、これについては『ネオディケイドライドウォッチ』の動画の17:50あたりから詳しく解説していますので、そちらを参照頂くのが良いと思います。動画内でも言及していますが、このルールの理解には、クレーンの丈様が提供してくださった解析情報を大いに活用させて頂いております。この場を借りてお礼申し上げます、ありがとうございます。

ソースコードの全文はこの記事の最後に掲載しますが、ネオディケイドとネオディエンドでほぼ一緒なので、ネオディケイドのソースのみ掲載致します。

ソースコード上でネオディケイドとネオディエンドで異なっているのは、電源ON時のボタン発光色の定義と、固有音声(←「〜は、ディエンドだ!」や「アーマータイム!」)の違いによる、顔部分の白色LEDの発光パターンの定義だけです。大きく異なるのは、ソースコードではなく、再生する音声ファイルの方です。

ディエンドの動画の解説内(17:10あたり)にて、「主役ライダーの識別番号を読み取ったら、主役ライダーの音声の代わりにサブライダーの音声を再生するだけ」みたいなことを書いていますが、実装の実態としては、それだけで済む話ではありません。

先の作品内容説明のところで、ネオディケイドライドウォッチは以下の5つの音声を再生可能であることを述べました。

  • カメンライド
  • ファイナルアタックライド
  • ファイナルフォームライド
  • ファイナルフォームタイム
  • ファイナルアタックタイムブレイク

これは、もう少し詳細に書くと、こうなります。

  • カメンライド(「カメンライド!」+「ライダー名」+「変身音」)
  • ファイナルアタックライド(「ファイナルアタックライド!」+「ライダー名」+「攻撃音」)
  • ファイナルフォームライド(「ファイナルフォームライド!」+「ライダー名」+「変形音」)
  • ファイナルフォームタイム(「ファイナルフォームタイム!」+「ライダー名」+「中間フォーム変身音」)
  • ファイナルアタックタイムブレイク(「ライダー名」+「ファイナルアタックタイムブレイク!」+「中間フォーム攻撃音」)

一方、ネオディエンドライドウォッチではこれがどうなるかというと、こうなります。

  • カメンライド(「カメンライド!」+「ライダー名」+「召喚音」+「変身音」)
  • ファイナルアタックライド(「ファイナルアタックライド!」+「ライダー名」+「攻撃音」)
  • ファイナルフォームライド(「ファイナルフォームライド!」+「ライダー名」+「変形音」)
  • ファイナルフォームタイム(「ファイナルフォームタイム!」+「ライダー名」+「召喚音」+「中間フォーム変身音」)
  • ファイナルアタックタイムバースト(「ライダー名」+「ファイナルアタックタイムバースト!」+「中間フォーム攻撃音」)

つまり、5種類の音声中3種類がディケイドとディエンドで音声の仕様が異なることになります。結果、「主役ライダーの音声の代わりにサブライダーの音声を再生する」だけでなく、ネオディケイドライドウォッチで対応していた全てのライダーについて、5種類中3種類の音声ファイルをディエンド用に作り変える必要がありました。

この結果、今回、ネオディケイド/ネオディエンドライドウォッチ向けに、音声ファイルを全部で530個ぐらい作成することになりました。これが、今回こんなに時間がかかってしまった主要因です。音声ファイルは途中で何度か作り直しているので、多分修正前のものまで含めたら600個ぐらい音声ファイルを編集したと思います。終わってみれば狂気の沙汰の気がしています。

そしてこれは備忘録的な記述ですが、音声ファイル関係のトライ&エラー(←SDカード上のファイルの削除、追加、上書き保存)を何度も繰り返しているうちに、ボタンを押したあとやライドウォッチをセットしたときの音声再生がどんどん遅延していくという現象が発生しました。最大で1s近く遅れるようになってしまったので非常に焦りましたが、DFPlayerに挿しているマイクロSDカードをフォーマット用のアプリで完全に上書きフォーマットして、それから再度音声ファイルをSDカードにコピーすると、遅延が解消されました。おそらく、音声ファイルの削除・追加を繰り返している内に、SDカード上に何かしらのゴミデータが溜まっていって、それが何か悪さをしていたのだと思います。以前にDFPlayerで大量の音声ファイルを扱ったときにも発生していた現象なので、DFPlayerを使って数百レベルの音声ファイルを扱うときの特有の問題なのかもしれません。試される方は、ご注意ください。

 

さて、完成品のソフトウェアとしての解説は以上なのですが、実は今回の作品、自分の当初目標からするとデグレした形での完成となっています。ネオディエンドの完成品では「主役ライダーの音声の代わりにサブライダーの音声を再生する」という形でサブライダーに対応させており、その結果として「主役ライダーの音声は再生できない」ということになってしまいましたが、本当は主役ライダーの音声も再生可能な方向で完成させる予定でした。

その方法は、サブライダー用の認識番号とピン配列を新規で独自に定義するというものです。

ライドウォッチの製品仕様では識別パターンは全部で120通りですが、これは動画でも解説しているとおり

  • 3つのピンが立っているパターンは、認識読み取りのサインのため、通常は出現しない
  • 一度出現したパターンは二度と出現しない

という2つの制約から来ています。しかし、そのルールに縛られなければ、理屈の上では7x7x7=343通りのパターンの識別が可能なはずです。しかし、既に決まっている製品仕様を崩さないように拡張しようとする場合は、「先頭が『|||』の並びのときのみ、同一パターンの出現を許可する」という新たなルール(というよりは、制限緩和)を加えることで、元々の6x5x4=120通りの認識パターンにさらに1x7x7=49通りの認識パターンが追加される形になり、合計で169通りの認識パターンになります。

初めはこの考え方でプログラムを組んでいたのですが、自分のプログラムの組み方では169通りに対応させると誤認識が発生しやすくなりそうでしたので、最終的には「先頭のみ『|||』の並びを許可する」というルール(制限緩和)だけを加えることで、6x5x4+1x6x5=150通りの認識パターンを読み取れるプログラムを作成しました。そして、本来は存在していないNo.121以降にサブライダーの音声を割り当て、またそのNo.に対応する認識ピンのプレートを新規に作成することで、主役ライダーとサブライダーの音声を両立させる予定でした。

ところが、これを実際に動作させると、誤認識が多発してしまいました。

原因を精査したところ、問題はプログラムではなく認識ピンのプレート側にあることがわかりました。

新規の認識プレートは、既存商品のプレートに自分でピンを追加してマスタープレートを作成したのち、それをシリコンで型取りしてレジンで複製するという手法で量産したのですが、どうも「マスタープレート」のピン追加の精度が悪かったようです。パッと見は3つのピンが揃って並んでいるように見えても、実際はその中のどれかが先に読み取りスイッチに当たってしまう、というようなケースが多発しているようでした。

新規にシリコン型を作り直すという方向性もありましたが、それだと満足いく精度の型がいつできるかの目処が立たず、また全てのサブライダーのライドウォッチの認識プレートを交換するというのも決して楽な作業ではなく、遊びにくいことは遊びにくいため、今回は主役ライダーの音声には対応しないものの、ライドウォッチ側は無改造で遊べる現行方式を採用することにしました。なお、プログラム自体はNo.150まで読み取れる状態のままになっていて、ネオディケイドの動画の『おまけ』の音声はNo.128に割り当てています。No.128の認識プレートは、2段目・3段目の配列ピンがそれぞれ1つずつしか立たないため、誤認識が発生しにくく、何とか実用に耐え得る程度のものにはなっています。

また、プログラムには問題はないとしたものの、同じようにレジン複製で作成したネオディエンドライドウォッチ自体の認識プレートは、ジクウドライバーは正しく『ディエンド』と認識してくれているため、プログラムの方も認識ピンの精度の良し悪しに左右されない、もっとノイズに強いロジックの組み方があるのかもしれません。それは、今後の改善ポイントです。

まとめ

というわけで、『ネオディケイドライドウォッチ』と『ネオディエンドライドウォッチ』のご紹介でした。今回はどちらも発想としては素直ですが、ディケイド&ディエンドの「他のライダーに変身/他のライダーを召喚」という特性のせいで、音声を用意するのがただただひたすら面倒でした。そういう意味では、真似されにくい、価値のあるものにはなったのではないかと思います(←あまりに面倒なので多分誰もやらない、という意味で)。

今回の作品は、塗装は#りんちゃんねるのK様、認識ピンの解析はクレーンの丈様と、他の方々の協力あってこそ完成したものですが、もう一人、兄にも多大に協力を頂きました。

冒頭の「開発経緯」で、兄のアドバイスからネオディエンドライドウォッチが生まれることになったのは既に述べましたが、今回の作品ではとにかくいろんなライダーの変身音&攻撃音が必要で、その音声の録音に、兄が所有していた玩具コレクションが大いに役に立ちました。

私:「CSMのドラグバイザーの音声録音させてくれ」

兄:「ええよ」

私:「あ、パーフェクトゼクターもあるやん。これも録音させてくれ」

兄:「ええよ」

私:「まさかとは思うけどゴーバ…」

兄:「あるで」

こんな感じでした。どんだけ持ってんだ。

 

さて、最近はYouTubeの動画の中で作品のポイントについての説明はしているため、大半の方は動画だけ見てこちらの詳細記事は見ていないと思うのですが、せっかくここを訪れてくださった方向けに、最後に動画では説明していなかった、今回の作品の裏テーマについてお話しておきたいと思います。

今回裏テーマは2つあって、まず1つは『トランスチームガン・カスタムのリベンジ』です。

トランスチームガン・カスタム』というのは、一年と少し前に私が作った作品で、フルボトルの認識ピンをプログラムで読み取り、自分自身で好きなフルボトル同士の組み合わせ、ベストマッチを作り出せるという作品です。これは自分的には結構画期的で、すごいものができたと思っていたのですが、自分の他の作品(マリオガシャット、スマブラガシャット、ジーニアスフルボトル、カイザフォンXなど)と比べると、かなり低い評価に終わってしまいました。

その要因としては、最終的に出来上がったものの詰めが甘く、挙動が全体的にもっさりしたものになってしまって完成度が低くなってしまったというのもあるのですが、それ以上に、『見た目がただのトランスチームガンと何も変わらない』というところが大きかったのではないかと、個人的には思っています。いくら中で凄いことをやっていても、パッと見ではそれが何も伝わらない、それを痛感する出来事でした。

そういうこともあって、『トランスチームガン・カスタム』を作ったときに作成した『認識ピンを読み取るプログラム』の要素は、いつか他の作品作りに活かしたいというのは、ずっと思っていたことでした。そして、それを活かすときには『トランスチームガン・カスタム』と同じ轍は踏むまい、ということも考えていました。

今回、ネオディケイドライドウォッチの作成によって『認識ピンを読み取るプログラム』を使った作品は出来上がるだろうとは考えましたが、これもやはり、『トランスチームガン・カスタム』と同様に『見た目はただのディケイドライドウォッチ』で終わってしまう可能性が高いという懸念はありました。そんな折、兄のコメントからネオディエンドライドウォッチの作成という方向が示され、それがあれば見た目のインパクトという問題も、ディケイド単品で臨むよりは遥かにマシだろうと思いましたので、今回2つのライドウォッチを製作することにしました。

もう1つの裏テーマは、これは些細なものですが、『カービィへの贖罪』です。

すでにネオディケイドの方の動画を見ていただいているという前提で話を進めますが、『おまけ』のコーナーで『星のカービィ』の主人公であるカービィを題材とした『カービィライドウォッチ』が出てきます。これは、ディケイドとカービィが共に『ピンクの悪魔』と称されることがあることに因んだジョークなのですが、カービィへの個人的な『お詫び』でもあります。

というのも、実は過去にカービィを題材にしたガシャットを作るという構想があったものの、結局それを実現できずじまいになってしまっていたからです。『スマッシュブラザーズガシャット』を作ったときにカービィを対応させることができなかったため、その後に『マキシマムマイティXガシャット』が出た時に、「あ、これでロボボプラネット版のカービィのガシャットを作ればピッタリじゃん!」というアイデアは思いついていたのですが、自分がそこまでカービィのゲームの熱心なファンでもなかったというのと、あとちょうどその頃から長男との生活が始まって製作の時間がとれなくなってしまい、今日の今日まで来てしまいました。今回の機会に、ガシャットを作ってあげられなかったことに対するお詫びが少しでもできれば良いなあと思い、カービィだけ特別にライドウォッチを作成してみた次第です。

 

説明は以上になります。長文に最後までお付き合い頂きありがとうございました。

次の作品の構想については、なんとなくイメージはあるものの、自分の今の技術レベルだと非常に困難なので、実際に作るとなると、今回以上に時間がかかってしまうかもしれません。また、失敗する可能性も現状だと非常に高そうなので、正直やるかやらないかは迷い中、といったところです。

また、それとは別に、これまでの延長線上でまだ少しやり残していることもあるので、そちらに先に取り組むことになるかもしれません。

いつものごとく予定は未定ですが、今回はかなりしんどめの作品作りになってしまったので、できれば次はもっと簡単なのをサクッとやってしまいたいなあと思っています。

ソースコード

//--------------------------------------------------------------------------//

// 基本定義

#define READER_A_PIN  2 // 認識プレートに向かって右のピン
#define READER_B_PIN  3 // 真ん中のピン
#define READER_C_PIN  4 // 認識プレートに向かって左のピン
#define LED_WHITE_PIN 5 // 白色LEDはPWMが使用可能なピン(3,5,6,9,10,11)に設定
#define LED_COLOR_PIN 6
#define SW_C_PIN 7
#define SW_1_PIN 8
#define SW_2_PIN 9

#define LOOP_INTERVAL_MS 10

#define ON  LOW
#define OFF HIGH

#define N_BUTTON 3

// 3つのボタン入力と3つの読み取りピンの表現を兼ねる
const uint8_t OFF_OFF_OFF[] = {OFF, OFF, OFF}; // 読み取り時、区切り
const uint8_t OFF_OFF_ON[]  = {OFF, OFF,  ON}; // 読み取り時、スタート 0
const uint8_t OFF_ON_OFF[]  = {OFF,  ON, OFF}; // 読み取り時、スタート 1
const uint8_t OFF_ON_ON[]   = {OFF,  ON,  ON}; // 読み取り時、スタート 2
const uint8_t ON_OFF_OFF[]  = {ON,  OFF, OFF}; // 読み取り時、スタート 3
const uint8_t ON_OFF_ON[]   = {ON,  OFF,  ON}; // 読み取り時、スタート 4
const uint8_t ON_ON_OFF[]   = {ON,   ON, OFF}; // 読み取り時、スタート 5
const uint8_t ON_ON_ON[]    = {ON,   ON,  ON}; // 読み取り時、認識終了(+ 拡張領域用にスタート 6)
// ON, ON, ONの読み取りを有効にして、121番目以降では同一ピン配列の出現を許可することで、
// 追加で最大49個の番号(121〜169)を設定可能。
// ただし、今回はライドウォッチのIN方向/OUT方向を「同一ピンの複数出現」で判定するようなプログラムにしているため、
// 121番目以降も同一ピン配列の出現は許可していない。
// この場合、追加できる番号は30個(121〜150)になる。

#define STATE_SINGLE_A 1
#define STATE_SINGLE_B 2
#define STATE_READY_WP 3
#define STATE_WEAPON   4
#define STATE_READY_CH 5
#define STATE_CHANGED  6
#define STATE_READY_CR 7
#define STATE_CRITICAL 8
#define STATE_SINGLE_A_RW  9
#define STATE_SINGLE_B_RW 10
#define STATE_READY_WP_RW 11
#define STATE_WEAPON_RW   12
#define STATE_READY_CH_RW 13
#define STATE_CHANGED_RW  14
#define STATE_CRITICAL_RW 15

#define SIDE_NONE 0
#define SIDE_ARMOR_TIME 1
#define SIDE_RIDER_TIME 2

uint8_t prev_state = STATE_SINGLE_A;
uint8_t state      = STATE_SINGLE_A;
uint8_t prev_side  = SIDE_NONE;
uint8_t side       = SIDE_NONE;
uint8_t rider_time_counter = 0;
uint8_t armor_time_counter = 0;
boolean is_from_rw_state = false;

uint8_t prev_sw[]  = {OFF, OFF, OFF};
uint8_t sw[] = {OFF, OFF, OFF};

//--------------------------------------------------------------------------//

// 認識ピン読取処理

#define N_READER_PINS 3

#define READ_STAGE_1ST 0
#define READ_STAGE_2ND 1
#define READ_STAGE_3RD 2
#define READ_STAGE_END 3
#define READ_STAGE_FIX 4
#define UNKNOWN_VALUE 255

#define WAIT_MS_READER_RESET 1000
unsigned long reader_reset_start_time = 0;

uint8_t before_reader_state[3]  = {OFF, OFF, OFF}; // C, B, Aの順
uint8_t current_reader_state[3] = {OFF, OFF, OFF}; // C, B, Aの順
uint8_t read_stage = READ_STAGE_1ST;
uint8_t read_values[3] = {UNKNOWN_VALUE,UNKNOWN_VALUE,UNKNOWN_VALUE};
uint8_t watch_number = UNKNOWN_VALUE;

//--------------------------------------------------------------------------//

// 効果音処理

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
SoftwareSerial ss_mp3_player(10,11); // RX, TX

// ----- 音声の考え方 -----
//   1〜169 カメンライド音声(「カメンライド/ジオウ!/変身音」)
// 170〜338 ファイナルアタックライド音声(「ファイナルアタックライド/ジ・ジ・ジ・ジオウ!/必殺音」)
// 339〜507 ファイナルフォームライド音声(「ファイナルフォームライド/ジ・ジ・ジ・ジオウ!/必殺音」)
// 508〜676 ファイナルフォームタイム音声(「ファイナルフォームタイム/ジ・ジ・ジ・ジオウ!/中間フォーム変身音」)
// 677〜845 ファイナルアタックタイムブレイク音声(「ジ・ジ・ジ・ジオウ!/ファイナルアタックタイムブレイク/必殺音」)
// 846〜854 ディケイドライドウォッチ固有音声

#define SOUND_POWER_ON       846
#define SOUND_SINGLE_A       847
#define SOUND_SINGLE_B       848
#define SOUND_RIDER_TIME     849
#define SOUND_ARMOR_TIME     850
#define SOUND_WEAPON         851
#define SOUND_CRITICAL_READY 852
#define SOUND_CRITICAL       853
#define SOUND_EJECT          854

#define SOUND_FINAL_ATTACK_RIDE_OFFSET 169
#define SOUND_FINAL_FORM_RIDE_OFFSET   338
#define SOUND_FINAL_FORM_TIME_OFFSET   507
#define SOUND_FINAL_ATTACK_TIME_BREAK_OFFSET 676

#define ARMOR_TIME_WAIT 6500
#define RIDER_TIME_WAIT 1000
#define CRITICAL_WAIT_SHORT 1000
#define CRITICAL_WAIT_LONG  2000

unsigned long sound_wait_start_time = 0;
boolean is_armor_time_waiting = false;
boolean is_rider_time_waiting = false;
boolean is_armor_time_critical_waiting = false;
boolean is_rider_time_critical_waiting = false;

DFRobotDFPlayerMini mp3_player;

void play_sound(){
  if(prev_state != state){
    Serial.print(F("Prev State: "));
    Serial.println(prev_state);
    Serial.print(F("State: "));
    Serial.println(state);

    switch(state){
    case STATE_SINGLE_A:
      switch(prev_state){
      case STATE_SINGLE_B:
        mp3_player.playMp3Folder(SOUND_SINGLE_A);
        break;
      case STATE_READY_WP:
      case STATE_WEAPON:
      case STATE_READY_CH:
      case STATE_CHANGED:
      case STATE_READY_CR:
      case STATE_CRITICAL:
        mp3_player.playMp3Folder(SOUND_EJECT);
        break;
      default:
        mp3_player.pause();
      }
      break;
    case STATE_SINGLE_B:
      mp3_player.playMp3Folder(SOUND_SINGLE_B);
      break;
    case STATE_READY_WP:
      if(prev_state != STATE_WEAPON){
        mp3_player.pause();
      }
      break;
    case STATE_WEAPON:
      if(prev_state == STATE_READY_WP){
        mp3_player.playMp3Folder(SOUND_WEAPON);
      }else{
        mp3_player.pause();
      }
      break;
    case STATE_READY_CH:
      mp3_player.pause();
      break;
    case STATE_CHANGED:
      switch(prev_state){
      case STATE_CHANGED_RW:
      case STATE_CRITICAL_RW:
        mp3_player.pause();
        break;
      default:
        if(side == SIDE_RIDER_TIME){
          sound_wait_start_time = millis();
          is_rider_time_waiting = true;
        }else if(side == SIDE_ARMOR_TIME){
          sound_wait_start_time = millis();
          is_armor_time_waiting = true;
        }
      }
      break;
    case STATE_READY_CR:
      mp3_player.playMp3Folder(SOUND_CRITICAL_READY);
      break;
    case STATE_CRITICAL:
      if(side == SIDE_RIDER_TIME){
        mp3_player.pause();
        sound_wait_start_time = millis();
        is_rider_time_critical_waiting = true;
      }else if(side == SIDE_ARMOR_TIME){
        mp3_player.pause();
        sound_wait_start_time = millis();
        is_armor_time_critical_waiting = true;
      }
      break;
    case STATE_SINGLE_A_RW:
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        mp3_player.playMp3Folder(watch_number); // カメンライド
        break;
      case STATE_SINGLE_B_RW:
        mp3_player.playMp3Folder(watch_number + SOUND_FINAL_FORM_RIDE_OFFSET); // ファイナルフォームライド
        break;
      case STATE_READY_WP_RW:
      case STATE_WEAPON_RW:
      case STATE_READY_CH_RW:
      case STATE_CHANGED_RW:
      case STATE_CRITICAL_RW:
        mp3_player.playMp3Folder(SOUND_EJECT);
        break;
      default:
        ;
      }
      break;
    case STATE_SINGLE_B_RW:
      mp3_player.playMp3Folder(watch_number + SOUND_FINAL_ATTACK_RIDE_OFFSET);
      break;
    case STATE_READY_WP_RW:
      switch(prev_state){
      case STATE_READY_WP:
        mp3_player.playMp3Folder(watch_number); // カメンライド
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        mp3_player.pause();
        break;
      default:
        ;
      }
      break;
    case STATE_WEAPON_RW:
      if(state == STATE_WEAPON){
        mp3_player.playMp3Folder(watch_number); // カメンライド
      }else{
        mp3_player.playMp3Folder(SOUND_WEAPON);
      }
      break;
    case STATE_READY_CH_RW:
      if(prev_state == STATE_READY_CH){
        mp3_player.playMp3Folder(watch_number); // カメンライド
      }else{
        mp3_player.pause();
      }
      break;
    case STATE_CHANGED_RW:
      switch(prev_state){
      case STATE_CHANGED:
      case STATE_READY_CR:
      case STATE_CRITICAL:
        mp3_player.playMp3Folder(watch_number + SOUND_FINAL_FORM_TIME_OFFSET); // ファイナルフォームタイム
        break;
      case STATE_CRITICAL_RW:
        ;
        break;
      default:
        if(side == SIDE_RIDER_TIME){
          sound_wait_start_time = millis();
          is_rider_time_waiting = true;
        }else if(side == SIDE_ARMOR_TIME){
          sound_wait_start_time = millis();
          is_armor_time_waiting = true;
        }
      }
      break;
    case STATE_CRITICAL_RW:
      mp3_player.playMp3Folder(watch_number + SOUND_FINAL_ATTACK_TIME_BREAK_OFFSET); // ファイナルアタックタイムブレイク
      break;
    default:
      ;
    }
  }else{
    unsigned long now = millis();
    if(is_rider_time_waiting){
      if(now - sound_wait_start_time >= RIDER_TIME_WAIT){
          mp3_player.playMp3Folder(SOUND_RIDER_TIME);
          is_rider_time_waiting = false;
      }
    }else if(is_armor_time_waiting){
      if(now - sound_wait_start_time >= ARMOR_TIME_WAIT){
          mp3_player.playMp3Folder(SOUND_ARMOR_TIME);
          is_armor_time_waiting = false;
      }
    }else if(is_rider_time_critical_waiting){
      if(now - sound_wait_start_time >= CRITICAL_WAIT_LONG){
          mp3_player.playMp3Folder(SOUND_CRITICAL);
          is_rider_time_critical_waiting = false;
      }
    }else if(is_armor_time_critical_waiting){
      if(now - sound_wait_start_time >= CRITICAL_WAIT_SHORT){
          mp3_player.playMp3Folder(SOUND_CRITICAL);
          is_armor_time_critical_waiting = false;
      }
    }
  }
}

//--------------------------------------------------------------------------//

// フルカラーLED発光処理

#include <Adafruit_NeoPixel.h>
#define N_COLOR_LED 1
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_COLOR_LED, LED_COLOR_PIN, NEO_GRB);

// ライドウォッチ色定義
const uint8_t RW_COLORS[][3] PROGMEM = {
  {255,  0,255}, // No.  1  ZI-O  2018
  {255,  0,255}, // No.  2  ZI-O  2019
  {255,  0,255}, // No.  3  ZI-O  2038
  {255,  0,255}, // No.  4  ZI-O  2058
  {255,  0,255}, // No.  5  ZI-O  ****
  {255,  0,255}, // No.  6  ZI-O  2000
  {255,  0,255}, // No.  7  ZI-O  0000
  {255,  0,255}, // No.  8  ZI-O  1971
  {255,  0,255}, // No.  9  ZI-O  1989
  {  0,  0,  0}, // No. 10  RIDER NEXT
  {255,  0,255}, // No. 11  ZI-O  ライダ
  {255,  0,  0}, // No. 12  GEIZ  2068
  {255,  0,  0}, // No. 13  GEIZ  2019
  {255,  0,  0}, // No. 14  GEIZ  2038
  {255,  0,  0}, // No. 15  GEIZ  2058
  {255,  0,  0}, // No. 16  GEIZ  ****
  {255,  0,  0}, // No. 17  GEIZ  2000
  {255,  0,  0}, // No. 18  GEIZ  1001
  {255,  0,  0}, // No. 19  GEIZ  1971
  {255,  0,  0}, // No. 20  GEIZ  1989
  {255,  0,  0}, // No. 21  GEIZ  2020
  {255,  0,  0}, // No. 22  GEIZ  ライダ
  {  0,  0,  0}, // No. 23  TSUKUYOMI 2068
  {255,  0,255}, // No. 24  RIDER    NEXT(ジオウ2)
  {  0,  0,  0}, // No. 25  RIDER    xxxx
  {  0,  0,  0}, // No. 26  RIDER    2019
  {  0,  0,  0}, // No. 27  RIDER    ライダ
  {  0,  0,  0}, // No. 28  RIDER    0000
  {  0,  0,  0}, // No. 29  NEXT     0000
  {  0,  0,  0}, // No. 30  FUTURE   0000
  {  0,255,  0}, // No. 31  NEW      0000(ミライダー)
  {255,  0,  0}, // No. 32  KUUGA    2000
  {255,215,  0}, // No. 33  AGITO    2001
  {255,  0,  0}, // No. 34  RYUKI    2002
  {255,  0,  0}, // No. 35  FAIZ     2003
  {  0,  0,255}, // No. 36  BLADE    2004
  {255,  0,255}, // No. 37  HIBIKI   2005
  {255,  0,  0}, // No. 38  KABUTO   2006
  {255,  0,  0}, // No. 39  DEN-O    2007
  {255,  0,  0}, // No. 40  KIVA     2008
  {255,  0,255}, // No. 41  DECADE   2009
  {  0,255,  0}, // No. 42  W        2009
  {255,  0,  0}, // No. 43  OOO      2010
  {255,255,255}, // No. 44  FOURZE   2011
  {255,  0,  0}, // No. 45  WIZARD   2012
  {255,165,  0}, // No. 46  GAIM     2013
  {255,  0,  0}, // No. 47  DRIVE    2014
  {255,165,  0}, // No. 48  GHOST    2015
  {255,  0,255}, // No. 49  EX-AID   2016
  {255,  0,  0}, // No. 50  BUILD    2017
  {  0,  0,255}, // No. 51  CROSS-Z  2017
  {255,215,  0}, // No. 52  GREASE   2017
  {255,  0,255}, // No. 53  ROGUE    2017
  {  0,191,255}, // No. 54  BRAVE    2016
  {153,255,  0}, // No. 55  SNIPE    2016
  {255,255,  0}, // No. 56  LAZER    2016
  {  0,  0,255}, // No. 57  PARA-DX  2016
  {255,192,203}, // No. 58  POPPY    2016
  {  0,255,  0}, // No. 59  RIDER1   1971
  {  0,255,  0}, // No. 60  RIDER2   1971
  {255,  0,  0}, // No. 61  V3       1973
  {  0,  0,255}, // No. 62  RIDERMAN 1973
  {255,255,255}, // No. 63  X        1974
  {  0,255,  0}, // No. 64  AMAZON   1974
  {255,  0,  0}, // No. 65  STRONGER 1975
  {  0,255,  0}, // No. 66  SKYRIDER 1979
  {255,255,255}, // No. 67  SUPER-1  1980
  {255,  0,  0}, // No. 68  ZX       1984
  {  0,  0,  0}, // No. 69  BLACK    1987
  {  0,  0,  0}, // No. 70  BLACK RX 1988
  {  0,255,  0}, // No. 71  SHIN     1992
  {  0,255,  0}, // No. 72  ZO       1993
  {  0,255,  0}, // No. 73  J        1994
  {255,  0,255}, // No. 74  GENM     2016
  {  0,255,  0}, // No. 75  CRONUS   2016
  {255, 34, 34}, // No. 76  EVOL     2017
  {  0,  0,  0}, // No. 77  SHOCKER  1971
  {255,  0,  0}, // No. 78  ALFA     2016
  {  0,255,  0}, // No. 79  OMEGA    2016
  {  0,  0,255}, // No. 80  NEO      2017
  {255,255,255}, // No. 81  RIDE     STRIKER
  {255,255,255}, // No. 82  ALL RIDERS   ライダ
  {255,  0,  0}, // No. 83  SUPER SENTAI -SS-
  {255,255,255}, // No. 84  HEISEI       -HR-
  {255,255,255}, // No. 85  SHOWA        -SR-
  {255,255,255}, // No. 86  KAMEN RIDER  ライダ
  {255,  0,255}, // No. 87  EVIL       EVIL
  {  0,  0,  0}, // No. 88  ANCIENT    0000
  {255,  0,255}, // No. 89  GAME       GAME
  {  0,  0,  0}, // No. 90  FUTURE     0000
  {255,  0,  0}, // No. 91  X MAS      1225
  {255,255,255}, // No. 92  SPECIAL    -SP-
  {  0,  0,255}, // No. 93  RIDER      0000(ゲイツリバイブ)
  {255,  0,255}, // No. 94  ZI-O       ライダ
  {  0,  0,  0}, // No. 95  SINGULAR   0000
  {  0,  0,  0}, // No. 96  NOTHING    0000
  {255,  0,  0}, // No. 97  MILLENNIUM 2000
  {  0,  0,  0}, // No. 98  MAX        MAX
  {  0,255,255}, // No. 99  DIEND      2009
  {255,255,255}, // No.100  GENIUS     FIN.
  {255,215,  0}, // No.101  MUTEKI     FIN.
  {255,255,255}, // No.102  MUGEN      FIN.
  {255,  0,  0}, // No.103  TRIDORON   FIN.
  {255,255,255}, // No.104  KIWAMI     FIN.
  {175,238,238}, // No.105  INFINITY   FIN.
  {  0,  0,255}, // No.106  COSMIC     FIN.
  {255,  0,255}, // No.107  PUTOTYRA   FIN.
  {  0,255,  0}, // No.108  XTREME     FIN.
  {255,255,255}, // No.109  COMPLETE   FIN.
  {255,215,  0}, // No.110  EMPEROR    FIN.
  {255,  0,  0}, // No.111  LINER      FIN.
  {255,  0,  0}, // No.112  HYPER      FIN.
  {255,  0,  0}, // No.113  ARMED      FIN.
  {255,215,  0}, // No.114  KING       FIN.
  {255,  0,  0}, // No.115  BLASTER    FIN.
  {255,  0,  0}, // No.116  SURVIVE    FIN.
  {255,  0,  0}, // No.117  SHINING    FIN.
  {  0,  0,  0}, // No.118  ULTIMATE   FIN.
  {255,255,255}, // No.119  RIDE       GADGET
  {  0,  0,  0}, // No.120  RIDER     ライダ
  { 50, 50,255}, // No.121  拡張       ナイト
  {255,255,  0}, // No.122  拡張       カイザ
  {255, 34, 34}, // No.123  拡張       ギャレン
  {  0,255,  0}, // No.124  拡張       ゼロノス
  {255,255,255}, // No.125  拡張       イクサ
  {255,  0,  0}, // No.126  拡張       アクセル
  {  0,255,  0}, // No.127  拡張       バース
  {255,192,203}, // No.128  拡張       メテオ → カービィ
  {255,215,  0}, // No.129  拡張       ビースト
  {255,  0,  0}, // No.130  拡張       バロン
  {255,255,255}, // No.131  拡張       マッハ
  {  0,  0,255}, // No.132  拡張       スペクター
  {  0,  0,  0}, // No.133  拡張       未定義
  {  0,  0,  0}, // No.134  拡張       未定義
  {  0,  0,  0}, // No.135  拡張       未定義
  {  0,  0,  0}, // No.136  拡張       未定義
  {  0,  0,  0}, // No.137  拡張       未定義
  {  0,  0,  0}, // No.138  拡張       未定義
  {  0,  0,  0}, // No.139  拡張       未定義
  {  0,  0,  0}, // No.140  拡張       未定義
  {  0,  0,  0}, // No.141  拡張       未定義
  {  0,  0,  0}, // No.142  拡張       未定義
  {  0,  0,  0}, // No.143  拡張       未定義
  {  0,  0,  0}, // No.144  拡張       未定義
  {  0,  0,  0}, // No.145  拡張       未定義
  {  0,  0,  0}, // No.146  拡張       未定義
  {  0,  0,  0}, // No.147  拡張       未定義
  {  0,  0,  0}, // No.148  拡張       未定義
  {255,  0,  0}, // No.149  拡張       ゴーバスターズ
  {255,192,203}  // No.150  拡張       カービィ
};

void color_led_on(uint8_t watch_index){ // 配列が0始まりなので、ライドウォッチの番号から-1した値を渡す
  pixels.setPixelColor(0, pixels.Color(pgm_read_byte(&(RW_COLORS[watch_index][0])),
                                       pgm_read_byte(&(RW_COLORS[watch_index][1])),
                                       pgm_read_byte(&(RW_COLORS[watch_index][2]))));
  pixels.show();
}

void color_led_off(){
  pixels.setPixelColor(0, pixels.Color(0,0,0));
  pixels.show();
}

//--------------------------------------------------------------------------//

// 白色LED発光処理

#define LED_COUNT_MAX 3000 // LOOP_INTERVAL_MSが10msで、30sまでの発光定義を想定
#define LED_BRIGHTNESS 255 // 255が上限値
uint16_t led_counter = LED_COUNT_MAX;
unsigned long prev_blink_time = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
boolean is_inc = false;

void led_base_pattern_on(){
  analogWrite(LED_WHITE_PIN, LED_BRIGHTNESS);
}

void led_base_pattern_off(){
  analogWrite(LED_WHITE_PIN, 0);
}

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

void led_base_pattern_inc(int interval_ms, uint8_t steps){
  unsigned long now = millis();
  if(inc_dim_start_time == 0){
    inc_dim_start_time = now;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now - inc_dim_start_time) / ms_per_step;
  uint8_t l_step = LED_BRIGHTNESS/steps;
  analogWrite(LED_WHITE_PIN, l_step*current_step);
  if(now - inc_dim_start_time > interval_ms){
    inc_dim_start_time = 0;
  }
}

void led_base_pattern_dim(int interval_ms, uint8_t steps){
  unsigned long now = millis();
  if(inc_dim_start_time == 0){
    inc_dim_start_time = now;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now - inc_dim_start_time) / ms_per_step;
  uint8_t l_step = LED_BRIGHTNESS/steps;
  analogWrite(LED_WHITE_PIN, l_step*(steps-current_step));
  if(now - inc_dim_start_time > interval_ms){
    inc_dim_start_time = 0;
  }
}

void led_base_pattern_blink_slowly(int interval_ms, uint8_t steps){
  unsigned long now = millis();
  if(inc_dim_start_time == 0){
    inc_dim_start_time = now;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now - inc_dim_start_time) / ms_per_step;
  uint8_t l_step = LED_BRIGHTNESS/steps;
  if(is_inc){
    analogWrite(LED_WHITE_PIN, l_step*current_step);
  }else{
    analogWrite(LED_WHITE_PIN, l_step*(steps-current_step));
  }
  if(now - inc_dim_start_time > interval_ms){
    is_inc = !is_inc;
    inc_dim_start_time = 0;
  }
}

void led_pattern_single_a(uint16_t led_counter_ms){ // 起動音+解説+「ディケイドだ!」
  if(led_counter_ms <= 300){                                  led_base_pattern_off();}
  else if(  300 < led_counter_ms && led_counter_ms <=   800){ led_base_pattern_blink(80);}
  else if(  800 < led_counter_ms && led_counter_ms <=  2300){ led_base_pattern_dim(1500, 20);}
  else if( 2300 < led_counter_ms && led_counter_ms <=  7100){ led_base_pattern_on();}
  else if( 7100 < led_counter_ms && led_counter_ms <=  9500){ led_base_pattern_blink(90);}
  else if( 9500 < led_counter_ms && led_counter_ms <= 10100){ led_base_pattern_off();}
  else if(10100 < led_counter_ms && led_counter_ms <= 11100){ led_base_pattern_on();}
  else{                                                       led_base_pattern_off();}
}

void led_pattern_single_b(uint16_t led_counter_ms){ // 起動音+起動音+「ディディディディケイド!」
  if(led_counter_ms <= 300){                                led_base_pattern_off();}
  else if( 300 < led_counter_ms && led_counter_ms <=  800){ led_base_pattern_blink(80);}
  else if( 800 < led_counter_ms && led_counter_ms <= 2300){ led_base_pattern_dim(1500, 20);}
  else if(2300 < led_counter_ms && led_counter_ms <= 2900){ led_base_pattern_dim(600, 10);}
  else if(2900 < led_counter_ms && led_counter_ms <= 3500){ led_base_pattern_dim(600, 10);}
  else if(3500 < led_counter_ms && led_counter_ms <= 4000){ led_base_pattern_dim(500, 10);}
  else if(4000 < led_counter_ms && led_counter_ms <= 4450){ led_base_pattern_blink(60);}
  else if(4450 < led_counter_ms && led_counter_ms <= 5900){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_weapon(uint16_t led_counter_ms){ // 「ディディディディケイド!
  if(led_counter_ms <= 300){                               led_base_pattern_off();}
  else if(300 < led_counter_ms && led_counter_ms <=  750){ led_base_pattern_blink(60);}
  else if(750 < led_counter_ms && led_counter_ms <= 2200){ led_base_pattern_on();}
  else{                                                    led_base_pattern_off();}

}

void led_pattern_rider_time(uint16_t led_counter_ms){ //
  if(led_counter_ms <= 1100){                               led_base_pattern_off();}
  else if(1100 < led_counter_ms && led_counter_ms <= 1900){ led_base_pattern_dim(800, 10);}
  else if(1900 < led_counter_ms && led_counter_ms <= 2800){ led_base_pattern_dim(900, 10);}
  else if(2800 < led_counter_ms && led_counter_ms <=14800){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_armor_time(uint16_t led_counter_ms){ //
  if(led_counter_ms <= 6600){                               led_base_pattern_off();}
  else if(6600 < led_counter_ms && led_counter_ms <= 7400){ led_base_pattern_dim(800, 10);}
  else if(7400 < led_counter_ms && led_counter_ms <= 8400){ led_base_pattern_dim(1000, 10);}
  else if(8400 < led_counter_ms && led_counter_ms <=20400){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_ready_critical(uint16_t led_counter_ms){ // 起動音+「ディディディディケイド!」+待機
  if(led_counter_ms <= 300){                                led_base_pattern_off();}
  else if( 300 < led_counter_ms && led_counter_ms <=  800){ led_base_pattern_blink(80);}
  else if( 800 < led_counter_ms && led_counter_ms <= 2300){ led_base_pattern_dim(1500, 20);}
  else if(2300 < led_counter_ms && led_counter_ms <= 2750){ led_base_pattern_blink(60);}
  else if(2750 < led_counter_ms && led_counter_ms <= 3750){ led_base_pattern_on();}
  else{                                                     led_base_pattern_blink(500);}
}

void led_pattern_critical_long(uint16_t led_counter_ms){ // 「アタック!」
  if(led_counter_ms <= 2400){                               led_base_pattern_off();}
  else if(2400 < led_counter_ms && led_counter_ms <= 2900){ led_base_pattern_blink(40);}
  else if(2900 < led_counter_ms && led_counter_ms <= 4400){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_critical_short(uint16_t led_counter_ms){ // 「アタック!」
  if(led_counter_ms <= 1400){                               led_base_pattern_off();}
  else if(1400 < led_counter_ms && led_counter_ms <= 1900){ led_base_pattern_blink(40);}
  else if(1900 < led_counter_ms && led_counter_ms <= 3400){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_kamen_ride(uint16_t led_counter_ms){ // 「カメンライド XXXX!」
  if(led_counter_ms <= 1200){                                led_base_pattern_off();}
  else if(1200 < led_counter_ms && led_counter_ms <= 16200){ led_base_pattern_on();}
  else{                                                      led_base_pattern_off();}
}

void led_pattern_final_attack_ride(uint16_t led_counter_ms){ // 「ファイナルアタックライド XXXX!」
  if(led_counter_ms <= 300){                                led_base_pattern_off();}
  else if( 300 < led_counter_ms && led_counter_ms <= 1800){ led_base_pattern_on();}
  else if(1800 < led_counter_ms && led_counter_ms <= 2250){ led_base_pattern_blink(60);}
  else if(2250 < led_counter_ms && led_counter_ms <=27250){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_final_form_ride(uint16_t led_counter_ms){ // 「ファイナルフォームライド XXXX!」
  if(led_counter_ms <= 300){                                led_base_pattern_off();}
  else if( 300 < led_counter_ms && led_counter_ms <= 1800){ led_base_pattern_on();}
  else if(1800 < led_counter_ms && led_counter_ms <= 2250){ led_base_pattern_blink(60);}
  else if(2250 < led_counter_ms && led_counter_ms <=10250){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_final_form_time(uint16_t led_counter_ms){ // 「ファイナルフォームタイム XXXX!」
  if(led_counter_ms <= 300){                                led_base_pattern_off();}
  else if( 300 < led_counter_ms && led_counter_ms <= 2100){ led_base_pattern_on();}
  else if(2100 < led_counter_ms && led_counter_ms <= 2550){ led_base_pattern_blink(60);}
  else if(2550 < led_counter_ms && led_counter_ms <=22550){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_final_attack_time_break(uint16_t led_counter_ms){ // 「XXXX! ファイナルアタックタイムブレイク !」
  if(led_counter_ms <= 300){                               led_base_pattern_off();}
  else if(300 < led_counter_ms && led_counter_ms <=  750){ led_base_pattern_blink(60);}
  else if(750 < led_counter_ms && led_counter_ms <=20750){ led_base_pattern_on();}
  else{                                                    led_base_pattern_off();}
}

void flash_led(){

  if(prev_state != state){
    switch(state){
    case STATE_SINGLE_A:
      if(prev_state == STATE_SINGLE_B){
        led_counter = 0;
      }else{
        led_counter = LED_COUNT_MAX; // 発光させないように、カウントの上限値にする
      }
      break;
    case STATE_READY_WP:
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        led_counter = LED_COUNT_MAX; // 発光させないように、カウントの上限値にする
        break;
      case STATE_WEAPON:
        ; // 発光を継続させるためにカウントをリセットしない
      default:
        led_counter = 0;
      }
      break;
    case STATE_SINGLE_A_RW:
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
      case STATE_SINGLE_B_RW:
        led_counter = 0;
        break;
      default:
        led_counter = LED_COUNT_MAX; // 発光させないように、カウントの上限値にする
      }
      break;
    case STATE_READY_WP_RW:
      switch(prev_state){
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        led_counter = LED_COUNT_MAX; // 発光させないように、カウントの上限値にする
        break;
      case STATE_WEAPON_RW:
        ; // 発光を継続させるためにカウントをリセットしない
      default:
        led_counter = 0;
      }
      break;
    case STATE_CHANGED_RW:
      if(prev_state == STATE_CRITICAL_RW){
        ; // 発光を継続させるためにカウントをリセットしない
      }else{
        led_counter = 0;
      }
      break;
    default:
      led_counter = 0; // 基本的に状態遷移が発生するとカウントをリセットする
    }

  }else if(prev_side != side){
    led_counter = 0;
  }

  uint16_t led_counter_ms = led_counter * LOOP_INTERVAL_MS; // LEDはms単位で制御

  switch(state){
  case STATE_SINGLE_A:
    led_pattern_single_a(led_counter_ms);
    break;
  case STATE_SINGLE_B:
    led_pattern_single_b(led_counter_ms);
    break;
  case STATE_READY_WP:
  case STATE_WEAPON:
    led_pattern_weapon(led_counter_ms);
    break;
  case STATE_READY_CH:
    led_base_pattern_blink(800);
    break;
  case STATE_CHANGED:
    if(side == SIDE_NONE){
      led_base_pattern_blink(800);
    }else if(side == SIDE_ARMOR_TIME){
      led_pattern_armor_time(led_counter_ms);
    }else if(side == SIDE_RIDER_TIME){
      led_pattern_rider_time(led_counter_ms);
    }
    break;
  case STATE_READY_CR:
    led_pattern_ready_critical(led_counter_ms);
    break;
  case STATE_CRITICAL:
    if(side == SIDE_ARMOR_TIME){
      led_pattern_critical_short(led_counter_ms);
    }else if(side == SIDE_RIDER_TIME){
      led_pattern_critical_long(led_counter_ms);
    }
    break;
  case STATE_SINGLE_A_RW:
    if(is_from_rw_state){
      led_pattern_final_form_ride(led_counter_ms); // ファイナルフォームライド
    }else{
      led_pattern_kamen_ride(led_counter_ms); // カメンライド
    }
    break;
  case STATE_SINGLE_B_RW:
    led_pattern_final_attack_ride(led_counter_ms);
    break;
  case STATE_READY_WP_RW:
    if(is_from_rw_state){
      led_pattern_weapon(led_counter_ms);
    }else{
      led_pattern_kamen_ride(led_counter_ms); // カメンライド
    }
    break;
  case STATE_WEAPON_RW:
    if(is_from_rw_state){
      led_pattern_weapon(led_counter_ms);
    }else{
      led_pattern_kamen_ride(led_counter_ms); // カメンライド
    }
    break;
  case STATE_READY_CH_RW:
    if(is_from_rw_state){
      led_base_pattern_blink(800);
    }else{
      led_pattern_kamen_ride(led_counter_ms); // カメンライド
    }
    break;
  case STATE_CHANGED_RW:
    if(is_from_rw_state){
      if(side == SIDE_NONE){
        led_pattern_final_attack_time_break(led_counter_ms); // ファイナルアタックタイムブレイク
      }else if(side == SIDE_ARMOR_TIME){
        led_pattern_armor_time(led_counter_ms);
      }else if(side == SIDE_RIDER_TIME){
        led_pattern_rider_time(led_counter_ms);
      }
    }else{
      led_pattern_final_form_time(led_counter_ms); // ファイナルフォームタイム
    }
    break;
  case STATE_CRITICAL_RW:
    led_pattern_final_attack_time_break(led_counter_ms); // ファイナルアタックタイムブレイク
    break;
  default:
    ;
  }

  if(led_counter < LED_COUNT_MAX){
    led_counter++;
  }

  pixels.show();
}

//--------------------------------------------------------------------------//

// リセット処理

void reset_rider_time_counter(){
  rider_time_counter = 0;
}

void reset_armor_time_counter(){
  armor_time_counter = 0;
}

void reset_all(){
  analogWrite(LED_WHITE_PIN, 0);
  reset_rider_time_counter();
  reset_armor_time_counter();
  is_armor_time_waiting = false;
  is_rider_time_waiting = false;
  is_armor_time_critical_waiting = false;
  is_rider_time_critical_waiting = false;
  led_counter = LED_COUNT_MAX;
}

//--------------------------------------------------------------------------//

// 起動処理

void setup() {
  Serial.begin(115200);
  pinMode(LED_WHITE_PIN, OUTPUT);
  pinMode(LED_COLOR_PIN, OUTPUT);
  pinMode(SW_C_PIN, INPUT_PULLUP);
  pinMode(SW_1_PIN, INPUT_PULLUP);
  pinMode(SW_2_PIN, INPUT_PULLUP);
  pinMode(READER_A_PIN,  INPUT_PULLUP);
  pinMode(READER_B_PIN,  INPUT_PULLUP);
  pinMode(READER_C_PIN,  INPUT_PULLUP);

  // ---------- MP3プレイヤーセットアップ ----------
  ss_mp3_player.begin(9600);
  if (!mp3_player.begin(ss_mp3_player)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin music_player:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  Serial.println(F("mp3_player online."));
  mp3_player.setTimeOut(500); //Set serial communictaion time out 500ms
  mp3_player.volume(20);  //Set volume value (0~30).

  // ---------- 起動エフェクト ----------
  mp3_player.playMp3Folder(SOUND_POWER_ON);
  analogWrite(LED_WHITE_PIN, LED_BRIGHTNESS);
  color_led_on(40); // ディケイド色
  delay(1500);
  analogWrite(LED_WHITE_PIN, 0);
  color_led_off();
}

//--------------------------------------------------------------------------//

// メイン処理

void loop() {
  unsigned long now = millis();
  prev_state = state;
  prev_side  = side;

  //-----------------------------------認識ピン読み取り処理-----------------------------------//

  // 認識ピンの状態読み取り
  current_reader_state[0] = digitalRead(READER_C_PIN);
  current_reader_state[1] = digitalRead(READER_B_PIN);
  current_reader_state[2] = digitalRead(READER_A_PIN);
  //Serial.print(current_reader_state[0]);
  //Serial.print(current_reader_state[1]);
  //Serial.println(current_reader_state[2]);

  if(memcmp(before_reader_state, current_reader_state, N_READER_PINS) != 0){ // ピン配列が変化したときのみ処理
    if(memcmp(current_reader_state, OFF_OFF_OFF, N_READER_PINS) == 0){ // 区切りの認識
      reader_reset_start_time = now; // リーダー初期化用のタイマーセット
      uint8_t read_value = !before_reader_state[0] * 4 + !before_reader_state[1] * 2 + !before_reader_state[2] * 1 - 1;
      // ライドウォッチを入れる方向か外す方向かは、「すでに読み取った値が再度読み込まれたか」で判断
      switch(read_stage){
      case READ_STAGE_1ST: // ライドウォッチを入れるときのみ発生
        read_values[0] = read_value;
        read_stage = READ_STAGE_2ND;
        break;
      case READ_STAGE_2ND:
        if(read_value == read_values[0]){ // ライドウォッチを外すとき
          read_values[0] = UNKNOWN_VALUE;
          read_stage = READ_STAGE_1ST;
          watch_number = UNKNOWN_VALUE;
          color_led_off();
          //mp3_player.pause();
          Serial.println(F("Read Ready."));
          switch(state){
          case STATE_SINGLE_A_RW: state = STATE_SINGLE_A; break;
          case STATE_SINGLE_B_RW: state = STATE_SINGLE_A; break;
          case STATE_READY_WP_RW: state = STATE_READY_WP; break;
          case STATE_WEAPON_RW:   state = STATE_WEAPON;   break;
          case STATE_READY_CH_RW: state = STATE_READY_CH; break;
          case STATE_CHANGED_RW:  state = STATE_CHANGED;  break;
          case STATE_CRITICAL_RW: state = STATE_CHANGED;  break;
          default: ;
          }
        }else{ // ライドウォッチを入れるとき
          read_values[1] = read_value;
          read_stage = READ_STAGE_3RD;
        }
        break;
      case READ_STAGE_3RD:
        if(read_value == read_values[1]){ // ライドウォッチを外すとき
          read_values[1] = UNKNOWN_VALUE;
          read_stage = READ_STAGE_2ND;
        }else{ // ライドウォッチを入れるとき
          read_values[2] = read_value;
          read_stage = READ_STAGE_END;
        }
        break;
      case READ_STAGE_END: // これが発生するのは、ライドウォッチを外すときのみ発生
        read_values[2] = UNKNOWN_VALUE;
        read_stage = READ_STAGE_3RD;
        break;
      case READ_STAGE_FIX: // これが発生するのは、ライドウォッチを外すときのみ発生
        read_stage = READ_STAGE_END;
        break;
      default:
        ;
      }
    }else if(read_stage == READ_STAGE_END && memcmp(current_reader_state, ON_ON_ON, N_READER_PINS) == 0){
      // 3段階分の読み取りステージをクリアしてからON, ON, ONを認識したときのみ、認識終了とする(拡張領域の確保のため)
      read_stage = READ_STAGE_FIX;
      uint8_t value_0 = read_values[0];
      uint8_t value_1 = read_values[1];
      uint8_t value_2 = read_values[2];

      if(read_values[0] < read_values[1]){
        // 同じピン配列が出てこないルールにより、2段目の読取値は、前段の読取値が2段目の読取値より小さい場合、-1する必要あり
        value_1 -= 1;
      }
      if(read_values[0] < read_values[2]){
        // 同じピン配列が出て来ないルールにより、3段目の読取値は、それ以前の読取値が3段目の読取値より小さい場合、該当する分だけマイナスする必要あり
        value_2 -= 1;
      }
      if(read_values[1] < read_values[2]){
        value_2 -= 1;
      }
      if(value_0 < 6){ // 製品版のピン配列ルール
        watch_number = value_0 * 20 + value_1 * 4 + value_2 * 1 + 1;
      }else{ // 独自拡張したピン配列の番号計算
        watch_number = value_0 * 20 + value_1 * 5 + value_2 * 1 + 1;
      }
      Serial.print(F("Watch Number:"));

      Serial.println(watch_number);
      color_led_on(watch_number-1);

      switch(state){
      case STATE_SINGLE_A: state = STATE_SINGLE_A_RW; is_from_rw_state = false; break;
      case STATE_SINGLE_B: state = STATE_SINGLE_A_RW; is_from_rw_state = false; break;
      case STATE_READY_WP: state = STATE_READY_WP_RW; is_from_rw_state = false; break;
      case STATE_WEAPON:   state = STATE_WEAPON_RW;   is_from_rw_state = false; break;
      case STATE_READY_CH: state = STATE_READY_CH_RW; is_from_rw_state = false; break;
      case STATE_CHANGED:  state = STATE_CHANGED_RW;  is_from_rw_state = false; break;
      case STATE_READY_CR: state = STATE_CHANGED_RW;  is_from_rw_state = false; break;
      case STATE_CRITICAL: state = STATE_CHANGED_RW;  is_from_rw_state = false; break;
      default: ;
      }
    }
  }

  before_reader_state[0] = current_reader_state[0];
  before_reader_state[1] = current_reader_state[1];
  before_reader_state[2] = current_reader_state[2];

  // 想定外のピン配列や認識エラーへの対応のため、読取初期状態以外で、所定時間リーダーピンがすべてOFFなら、強制的に読み取り状態をリセットする
  if(read_stage != READ_STAGE_1ST &&
     memcmp(current_reader_state, OFF_OFF_OFF, N_READER_PINS) == 0 &&
     now - reader_reset_start_time > WAIT_MS_READER_RESET){
    Serial.println(F("Reader Reset."));
    read_stage = READ_STAGE_1ST;
    read_values[0] = UNKNOWN_VALUE;
    read_values[1] = UNKNOWN_VALUE;
    read_values[2] = UNKNOWN_VALUE;
    watch_number   = UNKNOWN_VALUE;
    color_led_off();
    analogWrite(LED_WHITE_PIN, 0);
    mp3_player.pause();
    Serial.println(F("Read Ready."));
    switch(state){
    case STATE_SINGLE_A_RW: state = STATE_SINGLE_A; break;
    case STATE_SINGLE_B_RW: state = STATE_SINGLE_A; break;
    case STATE_READY_WP_RW: state = STATE_READY_WP; break;
    case STATE_WEAPON_RW:   state = STATE_WEAPON;   break;
    case STATE_READY_CH_RW: state = STATE_READY_CH; break;
    case STATE_CHANGED_RW:  state = STATE_CHANGED;  break;
    case STATE_CRITICAL_RW: state = STATE_CHANGED;  break;
    default: ;
    }
  }

  //------------------------------中央ボタン・ドライバーボタン処理------------------------------//

  sw[0] = digitalRead(SW_C_PIN);
  sw[1] = digitalRead(SW_1_PIN);
  sw[2] = digitalRead(SW_2_PIN);

  if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
    if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
        state = STATE_SINGLE_B;
        break;
      case STATE_SINGLE_B:
        state = STATE_SINGLE_A;
        break;
      case STATE_SINGLE_A_RW:
        state = STATE_SINGLE_B_RW;
        is_from_rw_state = true;
        break;
      case STATE_SINGLE_B_RW:
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        state = STATE_READY_WP;
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        state = STATE_READY_WP_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_ON,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        state = STATE_READY_CH;
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        state = STATE_READY_CH_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_ON_ON,   N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_OFF_OFF, N_BUTTON) == 0){
    if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        state = STATE_READY_WP;
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        state = STATE_READY_WP_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_ON,   N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        state = STATE_READY_CH;
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        state = STATE_READY_CH_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_ON_ON,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
    if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_CHANGED:
        reset_all();
        state = STATE_READY_CR;
        break;
      case STATE_READY_CR:
        reset_all();
        prev_state = STATE_CHANGED; // 擬似的に状態変化を起こす
        state = STATE_READY_CR;
        break;
      case STATE_CHANGED_RW:
        state = STATE_CRITICAL_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_CHANGED:
      case STATE_READY_WP:
      case STATE_READY_CH:
      case STATE_READY_CR:
        reset_all();
        state = STATE_SINGLE_A;
        break;
      case STATE_READY_WP_RW:
      case STATE_READY_CH_RW:
      case STATE_CHANGED_RW:
      case STATE_CRITICAL_RW:
        reset_all();
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      switch(prev_state){
      case STATE_READY_WP:
        state = STATE_WEAPON;
        break;
      case STATE_READY_CH:
      case STATE_CHANGED:
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_RIDER_TIME;
        }
        break;
      case STATE_READY_CR:
        rider_time_counter++;
        if(rider_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_RIDER_TIME;
        }
        break;
      case STATE_READY_WP_RW:
        state = STATE_WEAPON_RW;
        is_from_rw_state = true;
        break;
      case STATE_READY_CH_RW:
      case STATE_CHANGED_RW:
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH_RW; // 擬似的に状態変化を起こす
          state = STATE_CHANGED_RW;
          side  = SIDE_RIDER_TIME;
          is_from_rw_state = true;
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
    if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
        state = STATE_SINGLE_B;
        break;
      case STATE_SINGLE_B:
        state = STATE_SINGLE_A;
        break;
      case STATE_CHANGED:
        reset_all();
        state = STATE_READY_CR;
        break;
      case STATE_READY_CR:
        reset_all();
        prev_state = STATE_CHANGED; // 擬似的に状態変化を起こす
        state = STATE_READY_CR;
        break;
      case STATE_SINGLE_A_RW:
        state = STATE_SINGLE_B_RW;
        is_from_rw_state = true;
        break;
      case STATE_SINGLE_B_RW:
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      case STATE_CHANGED_RW:
        state = STATE_CRITICAL_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        state = STATE_READY_CH;
        break;
      case STATE_READY_CH:
      case STATE_CHANGED:
        armor_time_counter++;
        if(armor_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_ARMOR_TIME;
        }
        break;
      case STATE_READY_CR:
        armor_time_counter++;
        if(armor_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_ARMOR_TIME;
        }
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        state = STATE_READY_CH_RW;
        is_from_rw_state = true;
        break;
      case STATE_READY_CH_RW:
      case STATE_CHANGED_RW:
        armor_time_counter++;
        if(armor_time_counter == 2){
          prev_state = STATE_READY_CH_RW; // 擬似的に状態変化を起こす
          state = STATE_CHANGED_RW;
          side  = SIDE_ARMOR_TIME;
          is_from_rw_state = true;
        }
        break;
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_WEAPON:
      case STATE_CHANGED:
      case STATE_READY_CH:
      case STATE_READY_CR:
        reset_all();
        state = STATE_SINGLE_A;
        break;
      case STATE_WEAPON_RW:
      case STATE_CHANGED_RW:
      case STATE_READY_CH_RW:
      case STATE_CRITICAL_RW:
        reset_all();
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_ON_OFF,  N_BUTTON) == 0){
    if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_CRITICAL_RW){
        state = STATE_CHANGED_RW;
        side = SIDE_NONE;
        is_from_rw_state = true;
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_CHANGED:
      case STATE_READY_WP:
      case STATE_READY_CH:
      case STATE_READY_CR:
        reset_all();
        state = STATE_SINGLE_A;
        break;
      case STATE_CHANGED_RW:
      case STATE_READY_WP_RW:
      case STATE_READY_CH_RW:
      case STATE_CRITICAL_RW:
        reset_all();
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      switch(prev_state){
      case STATE_READY_WP:
        state = STATE_WEAPON;
        break;
      case STATE_READY_CH:
      case STATE_CHANGED:
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_RIDER_TIME;
        }
        break;
      case STATE_READY_CR:
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_RIDER_TIME;
        }
        break;
      case STATE_READY_WP_RW:
        state = STATE_WEAPON_RW;
        is_from_rw_state = true;
        break;
      case STATE_READY_CH_RW:
      case STATE_CHANGED_RW:
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH_RW; // 擬似的に状態変化を起こす
          state = STATE_CHANGED_RW;
          side  = SIDE_RIDER_TIME;
          is_from_rw_state = true;
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_OFF_ON,  N_BUTTON) == 0){
    if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(prev_state == STATE_CRITICAL_RW){
        state = STATE_CHANGED_RW;
        side = SIDE_NONE;
        is_from_rw_state = true;
      }
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        state = STATE_READY_CH;
        break;
      case STATE_READY_CH:
      case STATE_CHANGED:
        armor_time_counter++;
        if(armor_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_ARMOR_TIME;
        }
        break;
      case STATE_READY_CR:
        armor_time_counter++;
        if(armor_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_ARMOR_TIME;
        }
        break;
      case STATE_SINGLE_A_RW:
      case STATE_SINGLE_B_RW:
        state = STATE_READY_CH_RW;
        is_from_rw_state = true;
        break;
      case STATE_READY_CH_RW:
      case STATE_CHANGED_RW:
        armor_time_counter++;
        if(armor_time_counter == 2){
          prev_state = STATE_READY_CH_RW; // 擬似的に状態変化を起こす
          state = STATE_CHANGED_RW;
          side  = SIDE_ARMOR_TIME;
          is_from_rw_state = true;
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_WEAPON:
      case STATE_CHANGED:
      case STATE_READY_CH:
      case STATE_READY_CR:
        reset_all();
        state = STATE_SINGLE_A;
        break;
      case STATE_WEAPON_RW:
      case STATE_CHANGED_RW:
      case STATE_READY_CH_RW:
      case STATE_CRITICAL_RW:
        reset_all();
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, OFF_ON_ON,  N_BUTTON) == 0){
    if(memcmp(sw, ON_ON_ON, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_CHANGED:
        reset_all();
        state = STATE_READY_CR;
        break;
      case STATE_READY_CR:
      case STATE_CRITICAL:
        reset_all();
        prev_state = STATE_CHANGED; // 擬似的に状態変化を起こす
        state = STATE_READY_CR;
        break;
      case STATE_CHANGED_RW:
        state = STATE_CRITICAL_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_CHANGED:
        side = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
        break;
      case STATE_CRITICAL:
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
        break;
      case STATE_CHANGED_RW:
        side = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_WEAPON:
        state = STATE_READY_WP;
        break;
      case STATE_CHANGED:
        side = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
        break;
      case STATE_CRITICAL:
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
        break;
      case STATE_WEAPON_RW:
        state = STATE_READY_WP_RW;
        break;
      case STATE_CHANGED_RW:
        side = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      switch(prev_state){
      case STATE_WEAPON:
      case STATE_CHANGED:
      case STATE_READY_CH:
      case STATE_READY_CR:
      case STATE_CRITICAL:
        reset_all();
        state = STATE_SINGLE_A;
        break;
      case STATE_WEAPON_RW:
      case STATE_CHANGED_RW:
      case STATE_READY_CH_RW:
        reset_all();
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_ON_ON,   N_BUTTON) == 0){
    if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
      if(prev_state == STATE_CRITICAL_RW){
        state = STATE_CHANGED_RW;
        side = SIDE_NONE;
        is_from_rw_state = true;
      }
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      switch(prev_state){
      case STATE_CHANGED:
        side = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
        break;
      case STATE_CRITICAL:
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
        break;
      case STATE_CHANGED_RW:
        side = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      switch(prev_state){
      case STATE_WEAPON:
        state = STATE_READY_WP;
        break;
      case STATE_CHANGED:
        side = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
        break;
      case STATE_CRITICAL:
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
        break;
      case STATE_WEAPON_RW:
        state = STATE_READY_WP_RW;
        is_from_rw_state = true;
        break;
      case STATE_CHANGED_RW:
        side = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      switch(prev_state){
      case STATE_WEAPON:
      case STATE_CHANGED:
      case STATE_READY_CH:
      case STATE_READY_CR:
      case STATE_CRITICAL:
        reset_all();
        state = STATE_SINGLE_A;
        break;
      case STATE_WEAPON_RW:
      case STATE_CHANGED_RW:
      case STATE_READY_CH_RW:
      case STATE_CRITICAL_RW:
        reset_all();
        state = STATE_SINGLE_A_RW;
        is_from_rw_state = true;
        break;
      default:
        ;
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }
  }

  /*
  Serial.print("State: ");
  Serial.println(state);
  Serial.print("rider time counter: ");
  Serial.println(rider_time_counter);
  Serial.print("armor time counter: ");
  Serial.println(armor_time_counter);
  Serial.println("");
  */

  prev_sw[0] = sw[0];
  prev_sw[1] = sw[1];
  prev_sw[2] = sw[2];

  //-----------------------------------音声再生処理-----------------------------------//
  play_sound();

  //-----------------------------------LED発光処理-----------------------------------//
  flash_led();

  delay(LOOP_INTERVAL_MS);
}