レジェンドライドマグナム・改をつくる

今回はDXレジェンドライドマグナムをベースに、レジェンドライダーのライドケミートレカの認識機能を強化して、全てのライダーを個別認識可能にしてみました。各作品の1号・2号・ダークライダーも区別して認識します。

開発経緯

元ネタはレジェンドライドマグナム購入直後の私自身のXのポストです。

https://x.com/hidaka_hiroji/status/1731128178956181657

「まあ誰かやるかな」と思っていたら意外とやる人がいなかったので、自分なりに技術的に楽しめそうなアイデアを加えて自分でやってみることにしました。

ライドケミートレカは玩具としての認識の仕組みの都合上、レジェンドライダーのカードはそこそこ出ているものの、個別音声が鳴るのは基本的に各作品の1号ライダーと前作『ギーツ』のライダーのみになります。それ以外の、例えば各作品の2号ライダーは、1号ライダーと同じ音声が鳴るようになっています。

レジェンドライドマグナムでは、このあたりの違和感を減らすための工夫として、1号/2号ライダー問わず、「◯◯ライダー」という音声が鳴るようになっています。例えば、『ファイズ』と『カイザ』のカードは、どちらを読み込ませても「ファイズライダー」と鳴ります。シリーズ名を呼べば、それは確かに正しい。

でも、個人的にはやっぱり各ライダーの固有名を鳴らして欲しい。かといって、Xで呟いたようにディエンドライバーの基板を移植すると、それはただの『レジェンドライドマグナムの姿をしたディエンドライバー』であって、読み込めるのもライドケミートレカでなくライダーカードになってしまいます。

 

というわけで、ライドケミートレカで各ライダーを個別認識できればそれがベスト、ということになるのですが、実はそれを可能にするための仕組みは、ライドケミートレカには初めから仕込まれています。

カード下部のバーコード。これはスマホアプリ『ガッチャードファイル』で使用するもので、アプリの方ではこのコードを読み込むことで、各ライダーを個別に認識してバトルで使用できるようになっています。『ファイズ』はファイズ、『カイザ』はカイザです。

玩具の方では共通音声になってしまうのは、下部ではなく側部のバーコードを読み込んでいるからであって、つまり、玩具でも下部のバーコードを読めるようにすれば、個別のライダー名を鳴らすことができるハズです。

この考えは実は『ガッチャード』が始まってすぐの段階で持っていたのですが、その後にレジェンドライドマグナムが出て、これと組み合わせると収まりが良さそうと思いましたので、CSM / ver.20th / ネオ版 ディエンドライバー並みに個別認識が可能なレジェンドライドマグナムを作ってみることにしました。

特徴

まず見た目の印象を大きく変えていて、イメージとしては、ここまでちょいちょい触れているディエンドライバーに寄せています。そのため、カラーリングはブルーとシルバーが基本になっています。本来ならブルーじゃなくてシアンじゃないといけない気はしますが、細かいことは気にするな。

ディエンドに寄せているので、元々のレジェンドライドマグナムにあったハンドル&タイヤはありません。ディエンドにバイクはありませんからね。

(動画)

ギミック的には、すでに開発経緯で述べたとおりで、1号/2号/ ダークライダー問わず、すべてのレジェンドライダーのライドケミートレカを個別に認識可能です。

音の鳴り方は基本的にレジェンドライドマグナムを踏襲していますが、元々認識時に「〇〇ライダー」と鳴っていたところは、各ライダーの名前呼びに変わっています。元々の「〇〇ライダー」の音声は別のところで使うようにしていて、複数のカードを読み込んだ際に、その読み込んだライダーの作品がすべて同じ場合に、「デュオ」「トリオ」「マッシモ」の代わりに鳴るようにしてみました。つまり、『アギト』と『ナイト』を読み込んだら「デュオ」になりますが、『アギト』と『G3』なら「アギトライダー」になる、という感じです。なお、上限はオリジナルと同じで4人までです。

あとはちょっとしたところで、発光部のLEDを1個から3個に増やしています。レジェンドライダーの中には、明らかにメインカラーが1つより複数の方が良いライダーもいる(W、オーズ、ビルド)ので、これらのライダーを表現できるようにするためにLEDを追加しました。ただ、あんまり細かく色分けしてもよくわからない感じになるので、基本的には一色になるようにしています。読み込んだライダーに合わせて発光色が変化するようになっており、複数ライダー読込時は、基本的に順繰りに発光色が変わるようにしています。

ちなみに、レジェンドライダー以外の通常のライドケミートレカを読み込ませた場合は、エラーになるようにしています。理屈上はもちろん対応可能なのですが、労力に見合わないかなと思いましたので、今回はレジェンドライダー対応に特化させました。

ハードウェア解説

ここからは作りの解説です。まずは使用した具材から。

何はともあれレジェンドライドマグナムです。制作時点(2024年7月)では、テレビシリーズがまもなく終わりというところで、投げ売りが始まっています。

 

続いて今回のキモとなる、バーコードリーダーです。なかなかお高い…

ちなみにもう少し安いモジュールもあって、こちらも試してみたのですが、QRコードは問題なく読み込めるものの、なぜか一次元バーコードの認識率がイマイチ…ということで、お高いですがこちらのモジュールを使用しました。

 

バーコードリーダーの読み取り方向を変えるためのミラーです。好きなサイズにカットできるものを選んでいます。このあたりの必要性については後述します。

 

マイコンは今回初めて、M5Stamp S3を使用してみました。理由としては、先のバーコードリーダーがM5Stackの製品なので、マイコンもM5Stack製品にしておく方がすんなり動いてくれそうだったこと。もう一つは、GPIOが豊富なこと。今回の構想段階ではもうちょっと色々やるつもりだったので、GPIOがたくさんあるものを選びました。色々検証した結果、最終的には今の仕様に落ち着いて、そんなにGPIOは使わずに済みましたが。

ちなみに、M5Stamp S3のGPIOをフルに使おうと思うと、1.27mmピッチの半田付けが要求されます。私のように半田づけスキル的に厳しいという方は、こういうピッチ変換基板があった方が良いと思います。

 

毎度お馴染みMP3プレイヤーです。SDカード、スピーカーの用意も忘れずに。

 

フルカラーLEDは、NeoPixelのライブラリで制御できるものであればなんでもOKです。

 

今回、レジェンドライドマグナムの使える部品はそのまま流用しています。具体的には、電池ボックス、トリガースイッチ、カード認識開始スイッチ。ハンドルに対応するスイッチは、別でタクトスイッチを一つ用意しています。

あとは電子部品とは関係ないところとして、3Dプリンタで内部的な治具とか、ハンドルに代わるスイッチとか、タイヤがあった部分を埋めるパーツをつくっています。

それから塗装。これは簡単に、黒のサーフェイサー、シルバーとブルーメタリックのスプレーで塗装しています。

 

具材としてはこれで全部になります。電子部品の配線図はこんな感じです。

 

あとはこれをレジェンドライドマグナムに組み込むと、こんな感じになります。

組み込むにあたっては、いくつかポイントがあります。

まず簡単なところで、マイコンとMP3プレイヤーの配置。この作品は後々ライドケミートレカを入手してから発光や音声を追加したくなる可能性があるので、レジェンドライドマグナム本体のネジを開けなくてもUSBコネクタとSDカードにアクセスできるよう、本体に少し穴を開けています。

M5Stamp S3とDF Playerを固定するための治具は、3Dプリンタで作っています。他にも色々。

 

続いて、今回の一番のキモである、バーコードリーダーの配置です。モジュールが結構大きくてそもそも置けるスペースが限られる上に、バーコードとの距離が近過ぎると読み取れなくなってしまうので、配置場所がかなり限定されます。

で、色々考えた結果、最終的に以下のような配置になりました。

カードスロットの奥部に穴を開けて、カートの装填時にバーコード部分だけを露出させます。

そして、バーコードとリーダーの間にミラーを挟んで読み取り角度を変えることで、距離を稼ぎつつ、スペースの余裕があるところにリーダーを配置しています。

このミラーの配置の調整が非常にシビアで、僅かでも位置や角度が変わると認識しなくなってしまうので、良い感じの場所を探して固定するのがすごく大変でした。

 

こういう仕組みでバーコードを認識させているので、カードの装填時の向きが通常のレジェンドライドマグナムとは裏表が逆になっています。

これだとカードを装填しても外向きには何のライダーのカードが入っているかわからなくなるので、元々クリアゴールドだった部分をブルーのメタリック塗装で塗り潰してカードを見えなくすることで、カード装填後の違和感がないようにしています。

 

さて、これでバーコードの読み取りができるようになったのですが、実は素のレジェンドライドマグナムのままだと、ハンドルパーツが内部でバーコード読み取りのパスを塞いでしまいます。頑張れば避けられなくはないですが、読み取りの安定性が下がる可能性があります。

ということで、ハンドルパーツは撤去。代わりに押しボタンスイッチを埋め込むことにしました。ディエンドモチーフに寄せたのはこの辺りの事情もあって、ディエンドは元々バイクを持ってないので、バイクモチーフっぽさが減っても特に違和感がありません。

これに併せる形で、タイヤパーツもなくすことにしました。

タイヤの代わりに挟んでいるパーツは、3Dプリンタで作りました。パテ塗り&ヤスリがけで後加工を丁寧めにやったおかげで、結構良い感じの質感になりました。

ソフトウェア解説

ソースコードの全文は記事の最後に掲載するとして、ここではその理解のための補足をいくつか書いておきます。

まず、状態遷移図はこんな感じです。

 

ソースコードを読むときは、これを念頭に読んでいただくと、理解が進みやすいと思います。

続いてバーコードの読み込みについて。各ライダーごとに”KRXXXX”のようなコードを記載していますが、これは実際にバーコードリーダーを使って各ライダーのバーコードを読み込ませて判明した文字列を記載しています。

バーコードリーダーの詳しい仕様はこちらを見て頂きたいですが、プログラムとは別に、自分のやりたいことに合わせて、こちらにある設定用のQRコードを読み込む必要があります。私は今回は以下の設定を有効にしました。

  1. Factory Default Configuration
  2. Serial Port: TTL 232
  3. Key Holding: Manual mode-Key Holding
  4. Prompt sound: Mute: Open
  5. Data encoding format: 0 (Primitive Type)
  6. Code ID: Disable send Code ID
  7. 1D Reverse code reading: enable

上記は最終的な設定で、開発中は上記をベースにしつつ、以下を有効にしていました。

  • Prompt sound: Beeper Volume: Low (※Muteより、バーコードの読み取りの成否が判断しやすくなる)
  • Code ID: Enable send Code ID

今回の開発では別に知る必要はないのですが、Code IDの送信を有効にすると、ライドケミートレカで使用されているバーコードのフォーマットがわかるようになります。これによると、ライドケミートレカのコードは”D”。つまり、フォーマットとしては”Code 128, ISBT 128″になります。シンプルにコードを表示させるだけのサンプルプログラムとかは、こちらの方のコードが参考になると思います。

それから、バーコードの読み込み精度について。実は、最初にバーコードリーダーをレジェンドライドマグナムに組み込んだときには、読み込み失敗が多発して、文字列の後半数文字だけ読み込める、という状態になっていました。テストとしてブレッドボードを使って仮組みしているときには起こらなかった現象なので、理由はよくわかりませんが、リーダーとバーコードの距離が近過ぎるとこうなるのかもしれません。

これだと各ライダーのコードと完全マッチングできない、ということで、読み込めた文字列分(最低2文字)でマッチングする、という処理を入れました(1,219〜1,231行目)。これでマッチング精度は大きく改善したものの、それでも度々失敗が発生しました。

で、根本原因を考えてみた結果、マイコン側がリーダーに対して文字列を要求しに行くのが早過ぎて取りこぼしてるんじゃないか、という仮説を立ててみました。で、読み込みのwhileループに10msほどdelayを入れてみた結果(1,263行目)、仮説が当たっていたのか、無事に安定して読み込めるようになりました。

 

最後に、音声についての補足です。ライダー名の音声は基本的に『ネオディエンドライバー』の録音を使用していますが、そこに収録されていないものについては、ライドウォッチだったりレジェンドライバーだったりCSMディケイドライバー ver.2の音声を使用しています。

トリッキー(?)なのは『ブレイズ』と『ライブ』で、前者はネオディエンドライバーの「ブレイブ」と「クローズ」を切り貼りして作成、後者はレジェンドライバーの「ドライブ」の「ド」を削って音声を作っています。「ブレイズ」は、作ってみたらほぼ「ブレイブ」に聞こえるけど、まあそもそも「ブ」と「ズ」の違いしかないので、そりゃ区別し辛い…。

『バルカン』『アークゼロ』『エビル』『デモンズ』は、どうしても良い音声素材が見つからず、断念しました。「デモンズ」は、デモンズドライバーの音声だと、流石に声のトーンが違いすぎて。ちなみに、これらのライダーについては「仮面ライダー」の共通音声が鳴るようになっていますが、発光はちゃんとそれぞれのライダーにあった色で光るようにしています。なので、個別認識は一応できていることにはなります。

まとめ

以上、レジェンドライドマグナム・改のご紹介でした。『ガッチャード』は錬金術がメインテーマなので、自分の作品でもできれば錬金術的な要素を何か入れたかったのですが、特に妙案を思い付かず、ある種無難とも言えるレジェンド系のアイテムになりました。無難と言えば無難ですが、なかなかカッコ良いものができた気がするので、個人的には満足しています。久しぶりに直球(?)のアイテムを作った感じです。

次に作りたいものは決まっていますが、なかなか大変そうなので、じっくり基礎検証をしていきたいと思います。その間に『ガヴ』の何かが挟まるかもしれませんが。

 

ソースコード

以下、ソースコードの全文です。

#include <Arduino.h>

#define PIN_QR_RX     44
#define PIM_QR_TX     43
#define PIN_QR_DLED    1
#define PIN_QR_TRIG    3
#define PIN_SW_TRIGGER 5
#define PIN_SW_IGNITE  7
#define PIN_SW_READ    9
#define PIN_MP3_RX    13
#define PIN_MP3_TX    15
#define PIN_LED        0

#define ON  LOW
#define OFF HIGH
#define ANALOG_THRESHOLD 1000

uint8_t sw_trigger      = OFF;
uint8_t sw_ignite       = OFF;
uint8_t sw_read         = OFF;
uint8_t prev_sw_trigger = OFF;
uint8_t prev_sw_ignite  = OFF;
uint8_t prev_sw_read    = OFF;

#define STATE_INIT_1             0
#define STATE_INIT_2             1
#define STATE_BLAST_READY        2
#define STATE_READ_READY         3
#define STATE_READ_FAIL          4
#define STATE_SUMMON_READY       5
#define STATE_MULTI_SUMMON_READY 6
#define STATE_SUMMON_1           7
#define STATE_SUMMON_2           8
#define STATE_FINISH_READY       9

#define STATE_READY_WAIT_MS 20000
unsigned long state_start_point_ms = 0;

uint8_t state = STATE_INIT_1;
uint8_t prev_state = STATE_INIT_1;
uint8_t prev_diff_state = STATE_INIT_1; // 発光管理用

void change_state(uint8_t new_state){
  if(state != new_state){
    prev_diff_state = state;
  }
  state = new_state;
  Serial.print(F("State: "));
  switch(state){
  case STATE_INIT_1:             Serial.println(F("INIT_1"));             break;
  case STATE_INIT_2:             Serial.println(F("INIT_2"));             break;
  case STATE_BLAST_READY:        Serial.println(F("BLAST_READY"));        break;
  case STATE_READ_READY:         Serial.println(F("READ_READY"));         break;
  case STATE_READ_FAIL:          Serial.println(F("READ_FAIL"));          break;
  case STATE_SUMMON_READY:       Serial.println(F("SUMMON_READY"));       break;
  case STATE_MULTI_SUMMON_READY: Serial.println(F("MULTI_SUMMON_READY")); break;
  case STATE_SUMMON_1:           Serial.println(F("SUMMON_1"));           break;
  case STATE_SUMMON_2:           Serial.println(F("SUMMON_2"));           break;
  case STATE_FINISH_READY:       Serial.println(F("FINISH_READY"));       break;
  default: ;
  }
}

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

color_rgb COLOR_BLACK  = {  0,  0,  0};
color_rgb COLOR_WHITE  = {255,255,255};
color_rgb COLOR_RED    = {255,  0,  0};
color_rgb COLOR_GREEN  = {  0,255,  0};
color_rgb COLOR_BLUE   = {  0,  0,255};
color_rgb COLOR_YELLOW = {255,255,  0};
color_rgb COLOR_PURPLE = {255,  0,255}; // Magenta
color_rgb COLOR_CYAN   = {  0, 64,255}; // 定義から変更
color_rgb COLOR_ORANGE = {255,165,  0};
color_rgb COLOR_GOLD   = {255,215,  0};
color_rgb COLOR_PINK   = {255, 20,147}; // DeepPink
color_rgb COLOR_LIME   = { 50,205, 50}; // LimeGreen

typedef struct {
  char* code;
  uint8_t rider_id;
  uint8_t series_id; // 0が平成・令和以外、1〜はクウガから順
  color_rgb color_r;
  color_rgb color_c;
  color_rgb color_l;
} legend_rider;

legend_rider legend_riders[] = {
    // 1号ライダー(クウガ〜ガッチャード+1号+バイス+Reserved x 3)
    {"KRUL3E",  1,  1, COLOR_RED,    COLOR_RED,    COLOR_RED},    // クウガ
    {"KRADJ6",  2,  2, COLOR_GOLD,   COLOR_GOLD,   COLOR_GOLD},   // アギト
    {"KRJ258",  3,  3, COLOR_RED,    COLOR_RED,    COLOR_RED},    // 龍騎
    {"KRMDXJ",  4,  4, COLOR_RED,    COLOR_RED,    COLOR_RED},    // ファイズ
    {"KR72N0",  5,  5, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // ブレイド
    {"KR6Y2V",  6,  6, COLOR_PURPLE, COLOR_PURPLE, COLOR_PURPLE}, // 響鬼
    {"KRKVLK",  7,  7, COLOR_RED,    COLOR_RED,    COLOR_RED},    // カブト
    {"KRT000",  8,  8, COLOR_RED,    COLOR_RED,    COLOR_RED},    // 電王
    {"KR6WJK",  9,  9, COLOR_RED,    COLOR_RED,    COLOR_RED},    // キバ
    {"KRHCZL", 10, 10, COLOR_PINK,   COLOR_PINK,   COLOR_PINK}, // ディケイド
    {"KRRJ98", 11, 11, COLOR_GREEN,  COLOR_PURPLE, COLOR_PURPLE}, // W
    {"KR961A", 12, 12, COLOR_YELLOW, COLOR_RED,    COLOR_GREEN},  // オーズ
    {"KR517N", 13, 13, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // フォーゼ
    {"KRJLT7", 14, 14, COLOR_RED,    COLOR_RED,    COLOR_RED},    // ウィザード
    {"KRUGNV", 15, 15, COLOR_BLUE,   COLOR_ORANGE, COLOR_BLUE},   // 鎧武
    {"KRW8A8", 16, 16, COLOR_RED,    COLOR_RED,    COLOR_RED},    // ドライブ
    {"KRZCTV", 17, 17, COLOR_ORANGE, COLOR_ORANGE, COLOR_ORANGE}, // ゴースト
    {"KRFV56", 18, 18, COLOR_PURPLE, COLOR_PURPLE, COLOR_PURPLE}, // エグゼイド
    {"KR0F1H", 19, 19, COLOR_RED,    COLOR_RED,    COLOR_BLUE},   // ビルド
    {"KR5BGU", 20, 20, COLOR_WHITE,  COLOR_PURPLE, COLOR_WHITE},  // ジオウ
    {"KR3ECD", 21, 21, COLOR_YELLOW, COLOR_YELLOW, COLOR_YELLOW}, // ゼロワン
    {"KRUHV2", 22, 22, COLOR_RED,    COLOR_WHITE,  COLOR_BLACK},  // セイバー
    {"KRP2EV", 23, 23, COLOR_PINK,   COLOR_PINK,   COLOR_PINK},   // リバイ
    {"KRG51T", 24, 24, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // ギーツ
    {"KR08DT", 25, 25, COLOR_CYAN,   COLOR_CYAN,   COLOR_CYAN},   // ガッチャード
    {"KRWMYD", 26,  0, COLOR_GREEN,  COLOR_GREEN,  COLOR_GREEN},  // 1号
    {"KRH10B", 27, 23, COLOR_PINK,   COLOR_PINK,   COLOR_PINK},   // バイス
    {"KRxxxx", 28,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    {"KRxxxx", 29,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    {"KRxxxx", 30,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    // 2号ライダー+G3〜マジェード+2号+エビル+Reserved x 3)
    {"KRxxxx", 31,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    {"KRH972", 32,  2, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // G3
    {"KR72AN", 33,  3, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // ナイト
    {"KRX4ZD", 34,  4, COLOR_YELLOW, COLOR_YELLOW, COLOR_YELLOW}, // カイザ
    {"KRWERY", 35,  5, COLOR_RED,    COLOR_RED,    COLOR_RED},    // ギャレン
    {"KR2HTH", 36,  6, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // 威吹鬼
    {"KRRTH8", 37,  7, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // ガタック
    {"KRWBU2", 38,  8, COLOR_GREEN,  COLOR_GREEN,  COLOR_GREEN},  // ゼロノス
    {"KR3NVX", 39,  9, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // イクサ
    {"KRP3LL", 40, 10, COLOR_CYAN,   COLOR_CYAN,   COLOR_CYAN},   // ディエンド
    {"KRKZXD", 41, 11, COLOR_RED,    COLOR_RED,    COLOR_RED},    // アクセル
    {"KRJUMH", 42, 12, COLOR_GREEN,  COLOR_GREEN,  COLOR_GREEN},  // バース
    {"KR3MBA", 43, 13, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // メテオ
    {"KR7B94", 44, 14, COLOR_GOLD,   COLOR_GOLD,   COLOR_GOLD},   // ビースト
    {"KRYF8K", 45, 15, COLOR_RED,    COLOR_YELLOW, COLOR_RED},    // バロン
    {"KRZVYG", 46, 16, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // マッハ
    {"KRB7DM", 47, 17, COLOR_CYAN,   COLOR_CYAN,   COLOR_CYAN},   // スペクター
    {"KRUTK1", 48, 18, COLOR_CYAN,   COLOR_CYAN,   COLOR_CYAN},   // ブレイブ
    {"KR0L01", 49, 19, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // クローズ
    {"KR2TAB", 50, 20, COLOR_RED,    COLOR_YELLOW, COLOR_RED},    // ゲイツ
    {"KRCMZL", 51, 21, COLOR_BLUE,   COLOR_BLUE,   COLOR_WHITE},  // バルカン
    {"KRNTMC", 52, 22, COLOR_BLUE,   COLOR_BLUE,   COLOR_BLUE},   // ブレイズ
    {"KR7FKF", 53, 23, COLOR_GREEN,  COLOR_WHITE,  COLOR_GREEN},  // ライブ
    {"KRTV2F", 54, 24, COLOR_GREEN,  COLOR_GREEN,  COLOR_GREEN},  // タイクーン
    {"KRxxxx", 55, 25, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // マジェード
    {"KRPR7K", 56,  0, COLOR_RED,    COLOR_RED,    COLOR_RED},    // 2号
    {"KRM3VJ", 57, 23, COLOR_GREEN,  COLOR_BLACK,  COLOR_GREEN},  // エビル
    {"KRxxxx", 58,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    {"KRxxxx", 59,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    {"KRxxxx", 60,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // Reserved
    // その他のライダー
    {"KR6KW2", 61,  2, COLOR_GREEN,  COLOR_GREEN,  COLOR_GREEN},  // アナザーアギト
    {"KRZ5Z3", 62,  3, COLOR_PURPLE, COLOR_PURPLE, COLOR_PURPLE}, // 王蛇
    {"KRNXFA", 63,  5, COLOR_GREEN,  COLOR_GREEN,  COLOR_GREEN},  // レンゲル
    {"KRM2CP", 64,  7, COLOR_RED,    COLOR_RED,    COLOR_RED},    // ダークカブト
    {"KRHC8D", 65,  9, COLOR_RED,    COLOR_RED,    COLOR_RED},    // ダークキバ
    {"KR46Z3", 66, 11, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // エターナル
    {"KRJN20", 67, 17, COLOR_LIME,   COLOR_LIME,   COLOR_LIME},   // ネクロム
    {"KRRKA4", 68, 18, COLOR_PURPLE, COLOR_PURPLE, COLOR_PURPLE}, // ゲンム
    {"KRT9EN", 69, 19, COLOR_RED,    COLOR_GOLD,   COLOR_RED},    // エボル
    {"KRDF00", 70, 20, COLOR_GOLD,   COLOR_GOLD,   COLOR_GOLD},   // オーマジオウ
    {"KRJ0UY", 71, 21, COLOR_BLACK,  COLOR_RED,    COLOR_BLACK},  // アークゼロ
    {"KRUG8R", 72, 23, COLOR_RED,    COLOR_BLUE,   COLOR_RED},    // デモンズ
    {"KRVU1K", 73, 24, COLOR_GOLD,   COLOR_GOLD,   COLOR_GOLD},   // ナーゴ
    {"KRW7GY", 74, 24, COLOR_PURPLE, COLOR_PURPLE, COLOR_PURPLE}, // バッファ
    {"KRU5C5", 75, 24, COLOR_CYAN,   COLOR_CYAN,   COLOR_CYAN},   // ジーン
    {"KRRT1B", 76, 24, COLOR_LIME,   COLOR_LIME,   COLOR_LIME},   // ケケラ
    {"KRRE7C", 77, 24, COLOR_YELLOW, COLOR_YELLOW, COLOR_YELLOW}, // キューン
    {"KR9LMF", 78, 24, COLOR_PINK,   COLOR_PINK,   COLOR_PINK},   // ベロバ
    {"KRVRJN", 79, 24, COLOR_PURPLE, COLOR_PURPLE, COLOR_PURPLE}, // グレア
    {"KRA2DA", 80, 24, COLOR_WHITE,  COLOR_GOLD,   COLOR_WHITE},  // ゲイザー
    {"KR0CKR", 81, 25, COLOR_GOLD,   COLOR_GOLD,   COLOR_GOLD},   // レジェンド
    // 総選挙ライダー
    {"KRxxxx", 82,  5, COLOR_BLACK,  COLOR_RED,    COLOR_BLACK},  // カリス
    {"KRxxxx", 83, 18, COLOR_PINK,   COLOR_PINK,   COLOR_PINK},   // ポッピー
    {"KRxxxx", 84, 21, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE},  // アークワン
    {"KRxxxx", 85, 24, COLOR_BLUE,   COLOR_ORANGE, COLOR_BLUE},   // パンクジャック
    {"KRxxxx", 86,  0, COLOR_WHITE,  COLOR_WHITE,  COLOR_WHITE}   // ゼイン
}; 

uint8_t num_riders = sizeof(legend_riders) / sizeof(legend_rider);

uint8_t summon_riders_count = 0;
legend_rider summon_riders[4];
int8_t target_series = 0;

void summon_riders_series_check(){
  uint8_t series_count[26] = {0};

  for(uint8_t i=0;i<summon_riders_count;i++){
    series_count[summon_riders[i].series_id] += 1;
  }

  uint8_t zero_count = 0;

  for(uint8_t i=0;i<26;i++){
    if(series_count[i] == 0){
      // 一度も関連ライダーが読み込まれていないシリーズをカウント
      zero_count++;
    }else{
      // 関連ライダーが読み込まれているシリーズを保持。
      // 複数の場合は後で汎用音声に変えるので、後半のシリーズを残すロジックで良い
      target_series = i;
    }
  }

  if(zero_count < 25){
    // zero_countが25個未満ということは、複数シリーズのライダーが読み込まれていることになるので、汎用音声を鳴らす
    target_series = -1;
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <DFPlayerMini_Fast.h>
#include <SoftwareSerial.h>

#define VOLUME 20 // 0〜30

SoftwareSerial ss_mp3_player(PIN_MP3_RX, PIN_MP3_TX);
DFPlayerMini_Fast mp3_player;

#define SOUND_WAIT_SUMMON_READY_MS   600
#define SOUND_WAIT_SUMMON_1_1_MS     950
#define SOUND_WAIT_SUMMON_1_2_MS    1050
#define SOUND_WAIT_SUMMON_1_3_MS    1250
#define SOUND_WAIT_SUMMON_1_4_MS    1600
unsigned long sound_wait_start_point_ms = 0;
boolean is_sound_ready = false;

#define SOUND_FOLDER_COMMON         1
#define SOUND_FOLDER_SUMMON_RIDERS  2
#define SOUND_FOLDER_SUMMON_SERIES  3

#define SOUND_POWER_ON                 1
#define SOUND_SHOT                     2
#define SOUND_BLAST_READY              3
#define SOUND_BLAST                    4
#define SOUND_CARD_SET                 5
#define SOUND_CARD_ERROR               6
#define SOUND_SUMMON_READY             7
#define SOUND_CARD_EJECT               8
#define SOUND_CARD_EJECT_SUMMON_READY  9
#define SOUND_SUMMON_SHOT_1           10
#define SOUND_SUMMON_SHOT_2           11
#define SOUND_SUMMON_SHOT_3           12
#define SOUND_SUMMON_SHOT_4           13
#define SOUND_SUMMON_COMMON_1         14
#define SOUND_SUMMON_COMMON_2         15
#define SOUND_SUMMON_COMMON_3         16
#define SOUND_SUMMON_COMMON_4         17
#define SOUND_FINISH_READY            18
#define SOUND_FINISH                  19

void play_sound(uint8_t folder_num, uint8_t track_num){
  mp3_player.playFolder(folder_num, track_num);
  Serial.print(F("Play Folder: "));
  Serial.print(folder_num);
  Serial.print(F(", Track: "));
  Serial.println(track_num);
}

void pause_sound(){
  mp3_player.pause();
}

void control_sound(unsigned long now_ms){
  switch(prev_state){
  case STATE_INIT_1:
    switch(state){
    case STATE_INIT_2:      play_sound(SOUND_FOLDER_COMMON, SOUND_SHOT);        break;
    case STATE_BLAST_READY: play_sound(SOUND_FOLDER_COMMON, SOUND_BLAST_READY); break;
    case STATE_READ_READY:  play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_SET);    break;
    default: ;
    }
    break;
  case STATE_INIT_2:
    switch(state){
    case STATE_INIT_1:      play_sound(SOUND_FOLDER_COMMON, SOUND_SHOT);        break;
    case STATE_BLAST_READY: play_sound(SOUND_FOLDER_COMMON, SOUND_BLAST_READY); break;
    case STATE_READ_READY:  play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_SET);    break;
    default: ;
    }
    break;
  case STATE_BLAST_READY:
    switch(state){
    case STATE_INIT_1: play_sound(SOUND_FOLDER_COMMON, SOUND_BLAST); break;
    default: ;
    }
    break;
  case STATE_READ_READY:
    switch(state){
    case STATE_INIT_1:
      play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_EJECT);
      break;
    case STATE_READ_FAIL:
      play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_ERROR);
      break;
    case STATE_SUMMON_READY:
      sound_wait_start_point_ms = now_ms;
      is_sound_ready = true;
      break;
    default: ;
    }
    break;
  case STATE_READ_FAIL:
    switch(state){
    case STATE_INIT_1:
      play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_EJECT);
      break;
    default: ;
    }
    break;
  case STATE_SUMMON_READY:
    switch(state){
    case STATE_MULTI_SUMMON_READY: play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_EJECT_SUMMON_READY); break;
    case STATE_SUMMON_1:
      switch(summon_riders_count){
      case 1: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_1); break;
      case 2: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_2); break;
      case 3: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_3); break;
      case 4: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_4); break;
      default: ;
      }
      sound_wait_start_point_ms = now_ms;
      is_sound_ready = true;
      break;
    default: ;
    }
    break;
  case STATE_MULTI_SUMMON_READY:
    switch(state){
    case STATE_READ_READY: play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_SET); break;
    case STATE_SUMMON_1:
      switch(summon_riders_count){
      case 1: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_1); break;
      case 2: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_2); break;
      case 3: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_3); break;
      case 4: play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_SHOT_4); break;
      default: ;
      }
      sound_wait_start_point_ms = now_ms;
      is_sound_ready = true;
      break;
    default: ;
    }
    break;
  case STATE_SUMMON_1:
    switch(state){
    case STATE_SUMMON_2:     play_sound(SOUND_FOLDER_COMMON, SOUND_SHOT);         break;
    case STATE_FINISH_READY: play_sound(SOUND_FOLDER_COMMON, SOUND_FINISH_READY); break;
    case STATE_INIT_2:       play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_EJECT);   break;
    case STATE_READ_READY:   play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_SET);     break;
    default: ;
    }
    break;
  case STATE_SUMMON_2:
    switch(state){
    case STATE_SUMMON_1:     play_sound(SOUND_FOLDER_COMMON, SOUND_SHOT);         break;
    case STATE_FINISH_READY: play_sound(SOUND_FOLDER_COMMON, SOUND_FINISH_READY); break;
    case STATE_INIT_2:       play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_EJECT);   break;
    case STATE_READ_READY:   play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_SET);     break;
    default: ;
    }
    break;
  case STATE_FINISH_READY:
    switch(state){
    case STATE_SUMMON_1: play_sound(SOUND_FOLDER_COMMON, SOUND_FINISH);     break;
    case STATE_INIT_2:   play_sound(SOUND_FOLDER_COMMON, SOUND_CARD_EJECT); break;
    default: ;
    }
    break;
  default:
    ;
  }

  // 時間差処理
  if(state == STATE_SUMMON_READY && is_sound_ready && now_ms - sound_wait_start_point_ms > SOUND_WAIT_SUMMON_READY_MS){
    play_sound(SOUND_FOLDER_SUMMON_RIDERS, summon_riders[summon_riders_count-1].rider_id);
    is_sound_ready = false;
  }

  if(state == STATE_SUMMON_1 && is_sound_ready){
      switch(summon_riders_count){
      case 1:
        if(now_ms - sound_wait_start_point_ms > SOUND_WAIT_SUMMON_1_1_MS){
          /*
          if(target_series <= 0){ // 複数読み込みと平成・令和以外は汎用音声
            play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_COMMON_1);
          }else{
            play_sound(SOUND_FOLDER_SUMMON_SERIES, target_series);
          }
          */
          // ライダーが1体のときは全員汎用音声にする
          play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_COMMON_1);
          is_sound_ready = false;
        }
        break;
      case 2:
        if(now_ms - sound_wait_start_point_ms > SOUND_WAIT_SUMMON_1_2_MS){
          if(target_series <= 0){ // 複数読み込みと平成・令和以外は汎用音声
            play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_COMMON_2);
          }else{
            play_sound(SOUND_FOLDER_SUMMON_SERIES, target_series);
          }
          is_sound_ready = false;
        }
        break;
      case 3:
        if(now_ms - sound_wait_start_point_ms > SOUND_WAIT_SUMMON_1_3_MS){
          if(target_series <= 0){ // 複数読み込みと平成・令和以外は汎用音声
            play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_COMMON_3);
          }else{
            play_sound(SOUND_FOLDER_SUMMON_SERIES, target_series);
          }
          is_sound_ready = false;
        }
        break;
      case 4:
        if(now_ms - sound_wait_start_point_ms > SOUND_WAIT_SUMMON_1_4_MS){
          if(target_series <= 0){ // 複数読み込みと平成・令和以外は汎用音声
            play_sound(SOUND_FOLDER_COMMON, SOUND_SUMMON_COMMON_4);
          }else{
            play_sound(SOUND_FOLDER_SUMMON_SERIES, target_series);
          }
          is_sound_ready = false;
        }
        break;
      default: ;
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <Adafruit_NeoPixel.h>

#define N_LED 3

unsigned long led_pattern_start_point_ms = 0;
int prev_interval_ms = 0;
uint8_t prev_steps = 0;
unsigned long prev_action_point_ms = 0;
boolean is_blink_on = false;
boolean is_inc = true;
unsigned long inc_dim_start_point_ms = 0;

uint8_t summon_rider_index = 0;

// 本来はNEO_RGB指定が正しいはずだが、Color(r,g,b)の色指定でなぜかRとGが入れ替わってしまうため、NEO_GRB指定にしている
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_LED, PIN_LED, NEO_GRB);

void led_base_pattern_off(){
  for(uint8_t i=0;i<N_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
}

void led_base_pattern_on(struct color_rgb *color){
  for(uint8_t i=0;i<N_LED;i++){
    pixels.setPixelColor(i, pixels.Color(color->r,color->g,color->b));
  }
}

void led_base_pattern_on_multi(struct color_rgb *color_r, struct color_rgb *color_c, struct color_rgb *color_l){
  pixels.setPixelColor(0, pixels.Color(color_r->r,color_r->g,color_r->b));
  pixels.setPixelColor(1, pixels.Color(color_c->r,color_c->g,color_c->b));
  pixels.setPixelColor(2, pixels.Color(color_l->r,color_l->g,color_l->b));
}

void led_base_pattern_on_multi_rotate(unsigned long now_ms, int inverval_ms){
  if(now_ms - prev_action_point_ms >= inverval_ms){
    summon_rider_index++;
    if(summon_rider_index >= summon_riders_count){
      summon_rider_index = 0;
    }
    pixels.setPixelColor(0, pixels.Color(summon_riders[summon_rider_index].color_r.r, summon_riders[summon_rider_index].color_r.g, summon_riders[summon_rider_index].color_r.b));
    pixels.setPixelColor(1, pixels.Color(summon_riders[summon_rider_index].color_c.r, summon_riders[summon_rider_index].color_c.g, summon_riders[summon_rider_index].color_c.b));
    pixels.setPixelColor(2, pixels.Color(summon_riders[summon_rider_index].color_l.r, summon_riders[summon_rider_index].color_l.g, summon_riders[summon_rider_index].color_l.b));
    prev_action_point_ms = now_ms;
  }
}

void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, int inverval_ms){
  if(now_ms - prev_action_point_ms >= inverval_ms){
    if(is_blink_on){
      for(uint8_t i=0;i<N_LED;i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
    }else{
      for(uint8_t i=0;i<N_LED;i++){
        pixels.setPixelColor(i, pixels.Color(color->r,color->g,color->b));
      }
    }
    is_blink_on = !is_blink_on;
    prev_action_point_ms = now_ms;
  }
}

void led_base_pattern_blink_multi(struct color_rgb *color_r, struct color_rgb *color_c, struct color_rgb *color_l, unsigned long now_ms, int inverval_ms){
  if(now_ms - prev_action_point_ms >= inverval_ms){
    if(is_blink_on){
      for(uint8_t i=0;i<N_LED;i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
    }else{
      pixels.setPixelColor(0, pixels.Color(color_r->r,color_r->g,color_r->b));
      pixels.setPixelColor(1, pixels.Color(color_c->r,color_c->g,color_c->b));
      pixels.setPixelColor(2, pixels.Color(color_l->r,color_l->g,color_l->b));
    }
    is_blink_on = !is_blink_on;
    prev_action_point_ms = now_ms;
  }
}

void led_base_pattern_blink_multi_rotate(unsigned long now_ms, int inverval_ms){
  if(now_ms - prev_action_point_ms >= inverval_ms){
    if(is_blink_on){
      for(uint8_t i=0;i<N_LED;i++){
        pixels.setPixelColor(i, pixels.Color(0,0,0));
      }
      summon_rider_index++;
      if(summon_rider_index >= summon_riders_count){
        summon_rider_index = 0;
      }
    }else{
      if(summon_rider_index >= summon_riders_count){
        summon_rider_index = 0;
      }
      pixels.setPixelColor(0, pixels.Color(summon_riders[summon_rider_index].color_r.r, summon_riders[summon_rider_index].color_r.g, summon_riders[summon_rider_index].color_r.b));
      pixels.setPixelColor(1, pixels.Color(summon_riders[summon_rider_index].color_c.r, summon_riders[summon_rider_index].color_c.g, summon_riders[summon_rider_index].color_c.b));
      pixels.setPixelColor(2, pixels.Color(summon_riders[summon_rider_index].color_l.r, summon_riders[summon_rider_index].color_l.g, summon_riders[summon_rider_index].color_l.b));
    }
    is_blink_on = !is_blink_on;
    prev_action_point_ms = now_ms;
  }
}

void led_base_pattern_inc(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / 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;

  for(uint8_t i=0;i<N_LED;i++){
    pixels.setPixelColor(i, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));
  }
}

void led_base_pattern_inc_multi(struct color_rgb *color_r, struct color_rgb *color_c, struct color_rgb *color_l, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }

  uint8_t r_r_step = color_r->r/steps;
  uint8_t r_g_step = color_r->g/steps;
  uint8_t r_b_step = color_r->b/steps;
  uint8_t c_r_step = color_c->r/steps;
  uint8_t c_g_step = color_c->g/steps;
  uint8_t c_b_step = color_c->b/steps;
  uint8_t l_r_step = color_l->r/steps;
  uint8_t l_g_step = color_l->g/steps;
  uint8_t l_b_step = color_l->b/steps;

  pixels.setPixelColor(0, pixels.Color(r_r_step*current_step, r_g_step*current_step, r_b_step*current_step));
  pixels.setPixelColor(1, pixels.Color(c_r_step*current_step, c_g_step*current_step, c_b_step*current_step));
  pixels.setPixelColor(2, pixels.Color(l_r_step*current_step, l_g_step*current_step, l_b_step*current_step));
}

void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / 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;
  for(uint8_t i=0;i<N_LED;i++){
    pixels.setPixelColor(i, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
  }
}

void led_base_pattern_dim_multi(struct color_rgb *color_r, struct color_rgb *color_c, struct color_rgb *color_l, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }

  uint8_t r_r_step = color_r->r/steps;
  uint8_t r_g_step = color_r->g/steps;
  uint8_t r_b_step = color_r->b/steps;
  uint8_t c_r_step = color_c->r/steps;
  uint8_t c_g_step = color_c->g/steps;
  uint8_t c_b_step = color_c->b/steps;
  uint8_t l_r_step = color_l->r/steps;
  uint8_t l_g_step = color_l->g/steps;
  uint8_t l_b_step = color_l->b/steps;

  pixels.setPixelColor(0, pixels.Color(r_r_step*(steps-current_step), r_g_step*(steps-current_step), r_b_step*(steps-current_step)));
  pixels.setPixelColor(1, pixels.Color(c_r_step*(steps-current_step), c_g_step*(steps-current_step), c_b_step*(steps-current_step)));
  pixels.setPixelColor(2, pixels.Color(l_r_step*(steps-current_step), l_g_step*(steps-current_step), l_b_step*(steps-current_step)));
}

void led_base_pattern_blink_slowly(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / 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;

  if(is_inc){
    for(uint8_t i=0;i<N_LED;i++){
      pixels.setPixelColor(i, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));
    }
  }else{
    for(uint8_t i=0;i<N_LED;i++){
      pixels.setPixelColor(i, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
    }
  }

  if(now_ms - inc_dim_start_point_ms >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_point_ms = 0;
  }
}

void led_base_pattern_blink_slowly_multi(struct color_rgb *color_r, struct color_rgb *color_c, struct color_rgb *color_l, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }

  uint8_t r_r_step = color_r->r/steps;
  uint8_t r_g_step = color_r->g/steps;
  uint8_t r_b_step = color_r->b/steps;
  uint8_t c_r_step = color_c->r/steps;
  uint8_t c_g_step = color_c->g/steps;
  uint8_t c_b_step = color_c->b/steps;
  uint8_t l_r_step = color_l->r/steps;
  uint8_t l_g_step = color_l->g/steps;
  uint8_t l_b_step = color_l->b/steps;

  if(is_inc){
    pixels.setPixelColor(0, pixels.Color(r_r_step*current_step, r_g_step*current_step, r_b_step*current_step));
    pixels.setPixelColor(1, pixels.Color(c_r_step*current_step, c_g_step*current_step, c_b_step*current_step));
    pixels.setPixelColor(2, pixels.Color(l_r_step*current_step, l_g_step*current_step, l_b_step*current_step));
  }else{
    pixels.setPixelColor(0, pixels.Color(r_r_step*(steps-current_step), r_g_step*(steps-current_step), r_b_step*(steps-current_step)));
    pixels.setPixelColor(1, pixels.Color(c_r_step*(steps-current_step), c_g_step*(steps-current_step), c_b_step*(steps-current_step)));
    pixels.setPixelColor(2, pixels.Color(l_r_step*(steps-current_step), l_g_step*(steps-current_step), l_b_step*(steps-current_step)));
  }

  if(now_ms - inc_dim_start_point_ms >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_point_ms = 0;
  }
}

void led_base_pattern_inc_repeat(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / 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;

  for(uint8_t i=0;i<N_LED;i++){
    pixels.setPixelColor(i, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));
  }

  if(now_ms - inc_dim_start_point_ms >= interval_ms){
    inc_dim_start_point_ms = 0;
  }
}

void led_base_pattern_dim_multi_repeat_rotate(unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;

    summon_rider_index++;
    if(summon_rider_index >= summon_riders_count){
      summon_rider_index = 0;
    }
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }

  uint8_t r_r_step = summon_riders[summon_rider_index].color_r.r/steps;
  uint8_t r_g_step = summon_riders[summon_rider_index].color_r.g/steps;
  uint8_t r_b_step = summon_riders[summon_rider_index].color_r.b/steps;
  uint8_t c_r_step = summon_riders[summon_rider_index].color_c.r/steps;
  uint8_t c_g_step = summon_riders[summon_rider_index].color_c.g/steps;
  uint8_t c_b_step = summon_riders[summon_rider_index].color_c.b/steps;
  uint8_t l_r_step = summon_riders[summon_rider_index].color_l.r/steps;
  uint8_t l_g_step = summon_riders[summon_rider_index].color_l.g/steps;
  uint8_t l_b_step = summon_riders[summon_rider_index].color_l.b/steps;

  if(summon_rider_index >= summon_riders_count){
    summon_rider_index = 0;
  }
  pixels.setPixelColor(0, pixels.Color(r_r_step*(steps-current_step), r_g_step*(steps-current_step), r_b_step*(steps-current_step)));
  pixels.setPixelColor(1, pixels.Color(c_r_step*(steps-current_step), c_g_step*(steps-current_step), c_b_step*(steps-current_step)));
  pixels.setPixelColor(2, pixels.Color(l_r_step*(steps-current_step), l_g_step*(steps-current_step), l_b_step*(steps-current_step)));

  if(now_ms - inc_dim_start_point_ms >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_point_ms = 0;
  }
}

void led_pattern_shot(struct color_rgb *color, unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 300){
    led_base_pattern_on(color);
  }else if(300 <= passed_ms && passed_ms < 1000){
    led_base_pattern_dim(color, now_ms, 700, 20);
  }else if(1000 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_shot_multi(struct color_rgb color_r, struct color_rgb color_c, struct color_rgb color_l, unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 300){
    led_base_pattern_on_multi(&color_r, &color_c, &color_l);
  }else if(300 <= passed_ms && passed_ms < 1000){
    led_base_pattern_dim_multi(&color_r, &color_c, &color_l, now_ms, 700, 20);
  }else if(1000 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_blast_ready(struct color_rgb *color, unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
    led_base_pattern_on(color);
  }else if(500 <= passed_ms && passed_ms < 1200){
    led_base_pattern_dim(color, now_ms, 700, 20);
  }else if(1200 <= passed_ms){
    led_base_pattern_inc_repeat(color, now_ms, 1000, 20);
  }
}

void led_pattern_blast(struct color_rgb *color, unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 900){
    led_base_pattern_dim(color, now_ms, 900, 20);
  }else if(900 <= passed_ms && passed_ms < 1300){
    led_base_pattern_blink(color, now_ms, 50);
  }else if(1300 <= passed_ms && passed_ms < 1800){
    led_base_pattern_on(color);
  }else if(1800 <= passed_ms && passed_ms < 2500){
    led_base_pattern_dim(color, now_ms, 700, 20);
  }else if(2500 <= passed_ms && passed_ms < 3300){
    led_base_pattern_blink(color, now_ms, 50);
  }else if(3300 <= passed_ms && passed_ms < 3800){
    led_base_pattern_dim(color, now_ms, 500, 10);
  }else if(3800 <= passed_ms && passed_ms < 5500){
    led_base_pattern_on(color);
  }else if(5500 <= passed_ms && passed_ms < 6500){
    led_base_pattern_dim(color, now_ms, 1000, 20);
  }else if(6500 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_card_in_out(struct color_rgb *color, unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 300){
    led_base_pattern_on(color);
  }else if(300 <= passed_ms && passed_ms < 800){
    led_base_pattern_dim(color, now_ms, 500, 10);
  }else if(800 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_summon_ready(struct color_rgb color_r, struct color_rgb color_c, struct color_rgb color_l, unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 600){
    led_base_pattern_off();
  }else if(600 <= passed_ms && passed_ms < 1400){
    led_base_pattern_on_multi(&color_r, &color_c, &color_l);
  }else if(1400 <= passed_ms){
    led_base_pattern_blink_multi_rotate(now_ms, 440);
  }
}

void led_pattern_multi_summon_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 600){
    led_base_pattern_off();
  }else if(600 <= passed_ms){
    led_base_pattern_blink_multi_rotate(now_ms, 440);
  }
}

void led_pattern_summon_1(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
    led_base_pattern_on_multi_rotate(now_ms, 500);
  }else if(500 <= passed_ms && passed_ms < 1000){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 500, 10);
  }else if(1000 <= passed_ms && passed_ms < 2500){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(2500 <= passed_ms && passed_ms < 8000){
    led_base_pattern_blink_multi_rotate(now_ms, 400);
  }else if(8000 <= passed_ms && passed_ms < 9500){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 1500, 20);
  }else if(9500 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_summon_2(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 400){
    led_base_pattern_on_multi_rotate(now_ms, 200);
  }else if(400 <= passed_ms && passed_ms < 1000){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 600, 20);
  }else if(1000 <= passed_ms && passed_ms < 2000){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(2000 <= passed_ms && passed_ms < 2500){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 500, 10);
  }else if(2500 <= passed_ms && passed_ms < 3500){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(3500 <= passed_ms && passed_ms < 9000){
    led_base_pattern_blink_multi_rotate(now_ms, 400);
  }else if(9000 <= passed_ms && passed_ms <10500){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 1500, 20);
  }else if(10500 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_summon_3(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 600){
    led_base_pattern_on_multi_rotate(now_ms, 200);
  }else if(600 <= passed_ms && passed_ms < 1200){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 600, 20);
  }else if(1200 <= passed_ms && passed_ms < 2200){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(2200 <= passed_ms && passed_ms < 2700){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 500, 10);
  }else if(2700 <= passed_ms && passed_ms < 3700){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(3700 <= passed_ms && passed_ms < 9200){
    led_base_pattern_blink_multi_rotate(now_ms, 400);
  }else if(9200 <= passed_ms && passed_ms <10700){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 1500, 20);
  }else if(10700 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_summon_4(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 800){
    led_base_pattern_on_multi_rotate(now_ms, 200);
  }else if(800 <= passed_ms && passed_ms < 1400){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 600, 20);
  }else if(1400 <= passed_ms && passed_ms < 2400){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(2400 <= passed_ms && passed_ms < 2900){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 500, 10);
  }else if(2900 <= passed_ms && passed_ms < 3900){
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(3900 <= passed_ms && passed_ms < 9400){
    led_base_pattern_blink_multi_rotate(now_ms, 400);
  }else if(9400 <= passed_ms && passed_ms <10900){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 1500, 20);
  }else if(10900 <= passed_ms){
    led_base_pattern_off();
  }
}

void led_pattern_finish_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
    led_base_pattern_on(&COLOR_WHITE);
  }else if(500 <= passed_ms && passed_ms < 1500){
    led_base_pattern_dim(&COLOR_WHITE, now_ms, 1000, 20);
  }else if(1500 <= passed_ms && passed_ms < 4000){
    led_base_pattern_on(&COLOR_WHITE);
  }else if(4000 <= passed_ms){
    led_base_pattern_dim_multi_repeat_rotate(now_ms, 850, 20);
  }
}

void led_pattern_finish(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 400){
    led_base_pattern_on_multi_rotate(now_ms, 100);
  }else if(400 <= passed_ms && passed_ms < 1200){
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 800, 20);
  }else if(1500 <= passed_ms && passed_ms < 3500){
    //led_base_pattern_on(&COLOR_WHITE);
    led_base_pattern_on_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l);
  }else if(3500 <= passed_ms){
    //led_base_pattern_dim(&COLOR_WHITE, now_ms, 1500, 20);
    led_base_pattern_dim_multi(&summon_riders[summon_rider_index].color_r, &summon_riders[summon_rider_index].color_c, &summon_riders[summon_rider_index].color_l, now_ms, 1500, 20);
  }
}

void control_led(unsigned long now_ms){
  if(prev_state != state){
    led_pattern_start_point_ms = now_ms;
    inc_dim_start_point_ms= 0;
    is_inc = true;
    is_blink_on = false;
    led_base_pattern_off();
  }

  unsigned long passed_ms = now_ms - led_pattern_start_point_ms;

  switch(state){
  case STATE_INIT_1:
    switch(prev_diff_state){
    case STATE_BLAST_READY: led_pattern_blast(&COLOR_WHITE, passed_ms, now_ms);       break;
    case STATE_READ_READY:  led_pattern_card_in_out(&COLOR_WHITE, passed_ms, now_ms); break;
    case STATE_READ_FAIL:   led_pattern_card_in_out(&COLOR_WHITE, passed_ms, now_ms); break;
    default:                led_pattern_shot(&COLOR_WHITE, passed_ms, now_ms);
    }
    break;
  case STATE_INIT_2:
    switch(prev_diff_state){
    case STATE_BLAST_READY:  led_base_pattern_off(); break;
    case STATE_SUMMON_1:     led_pattern_card_in_out(&COLOR_WHITE, passed_ms, now_ms); break;
    case STATE_SUMMON_2:     led_pattern_card_in_out(&COLOR_WHITE, passed_ms, now_ms); break;
    case STATE_FINISH_READY: led_pattern_card_in_out(&COLOR_WHITE, passed_ms, now_ms); break;
    default:                 led_pattern_shot(&COLOR_WHITE, passed_ms, now_ms);
    }
    break;
  case STATE_BLAST_READY:
    led_pattern_blast_ready(&COLOR_WHITE, passed_ms, now_ms);
    break;
  case STATE_READ_READY:
    led_pattern_card_in_out(&COLOR_WHITE, passed_ms, now_ms);
    break;
  case STATE_SUMMON_READY:
    led_pattern_summon_ready(summon_riders[summon_riders_count-1].color_r,
                             summon_riders[summon_riders_count-1].color_c,
                             summon_riders[summon_riders_count-1].color_l,
                             passed_ms, now_ms);
    break;
  case STATE_MULTI_SUMMON_READY:
    led_pattern_multi_summon_ready(passed_ms, now_ms);
    break;
  case STATE_SUMMON_1:
    switch(prev_diff_state){
    case STATE_SUMMON_READY:
      switch(summon_riders_count){
      case 1: led_pattern_summon_1(passed_ms, now_ms); break;
      case 2: led_pattern_summon_2(passed_ms, now_ms); break;
      case 3: led_pattern_summon_3(passed_ms, now_ms); break;
      case 4: led_pattern_summon_4(passed_ms, now_ms); break;
      default: ;
      }
      break;
    case STATE_FINISH_READY:
      led_pattern_finish(passed_ms, now_ms);
      break;
    default: led_pattern_shot_multi(summon_riders[summon_rider_index].color_r,
                                    summon_riders[summon_rider_index].color_c,
                                    summon_riders[summon_rider_index].color_l,
                                    passed_ms, now_ms);
    }
    break;
  case STATE_SUMMON_2:
    switch(prev_diff_state){
    case STATE_FINISH_READY:  led_base_pattern_off(); break;
    default: led_pattern_shot_multi(summon_riders[summon_rider_index].color_r,
                                    summon_riders[summon_rider_index].color_c,
                                    summon_riders[summon_rider_index].color_l,
                                    passed_ms, now_ms);
    }
    break;
  case STATE_FINISH_READY:
    led_pattern_finish_ready(passed_ms, now_ms);
    break;
  default:
    ;
  }

  pixels.show();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, PIN_QR_RX, PIM_QR_TX);  

  pinMode(PIN_QR_TRIG,    OUTPUT);
  pinMode(PIN_QR_DLED,    INPUT);
  pinMode(PIN_SW_TRIGGER, INPUT_PULLUP);
  pinMode(PIN_SW_IGNITE,  INPUT_PULLUP);
  pinMode(PIN_SW_READ,    INPUT_PULLUP);
  pinMode(PIN_LED,        OUTPUT);

  // 省電力化のため、CPU周波数を少し落とす場合に有効化。最大240MHz。80MHz未満にするとNeoPixelが動かない
  setCpuFrequencyMhz(160);

  // バーコードリーダーの読み取り初期値をOFFにする
  digitalWrite(PIN_QR_TRIG, HIGH);

  // フルカラーLED初期化
  pixels.begin();
  pixels.clear();
  led_base_pattern_off();
  pixels.show();

  // MP3プレイヤーセットアップ
  ss_mp3_player.begin(9600);
  if(!mp3_player.begin(ss_mp3_player)) {
    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("dfplayer online."));
  delay(800); // 間を開けるのが短すぎるとコマンドが有効にならないので注意
  mp3_player.volume(VOLUME);
  delay(500);
  play_sound(SOUND_FOLDER_COMMON, SOUND_POWER_ON);
}

void loop() {

  sw_trigger = digitalRead(PIN_SW_TRIGGER);
  sw_ignite  = digitalRead(PIN_SW_IGNITE);
  sw_read    = digitalRead(PIN_SW_READ);

  unsigned long now_ms = millis();

  ////////////////// SW状態遷移管理 //////////////////

  switch(state){
  case STATE_INIT_1:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_INIT_2);
    }else if(prev_sw_ignite == OFF && sw_ignite == ON){
      change_state(STATE_BLAST_READY);
      state_start_point_ms = now_ms;
    }else if(prev_sw_read == OFF && sw_read == ON){
      change_state(STATE_READ_READY);
      summon_riders_count = 0;
    }
    break;
  case STATE_INIT_2:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_INIT_1);
    }else if(prev_sw_ignite == OFF && sw_ignite == ON){
      change_state(STATE_BLAST_READY);
      state_start_point_ms = now_ms;
    }else if(prev_sw_read == OFF && sw_read == ON){
      change_state(STATE_READ_READY);
      summon_riders_count = 0;
    }
    break;
  case STATE_BLAST_READY:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_INIT_1);
    }
    break;
  case STATE_READ_READY:
    if(prev_sw_read == ON && sw_read == OFF){
      change_state(STATE_INIT_1);
    }
    break;
  case STATE_READ_FAIL:
    if(prev_sw_read == ON && sw_read == OFF){
      change_state(STATE_INIT_1);
    }
    break;
  case STATE_SUMMON_READY:
    if(prev_sw_read == ON && sw_read == OFF){
      change_state(STATE_MULTI_SUMMON_READY);
    }else if(prev_sw_trigger == OFF && sw_trigger == ON){
      summon_riders_series_check();
      change_state(STATE_SUMMON_1);
    }
    break;
  case STATE_MULTI_SUMMON_READY:
    if(prev_sw_read == OFF && sw_read == ON){
      change_state(STATE_READ_READY);
    }else if(prev_sw_trigger == OFF && sw_trigger == ON){
      summon_riders_series_check();
      change_state(STATE_SUMMON_1);
    }
    break;
  case STATE_SUMMON_1:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_SUMMON_2);
      summon_rider_index++;
      if(summon_rider_index >= summon_riders_count){
        summon_rider_index = 0;
      }
    }else if(prev_sw_ignite == OFF && sw_ignite == ON){
      change_state(STATE_FINISH_READY);
      state_start_point_ms = now_ms;
    }else if(prev_sw_read == ON && sw_read == OFF){
      change_state(STATE_INIT_2);
    }else if(prev_sw_read == OFF && sw_read == ON){
      change_state(STATE_READ_READY);
      summon_riders_count = 0;
    }
    break;
  case STATE_SUMMON_2:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_SUMMON_1);
      summon_rider_index++;
      if(summon_rider_index >= summon_riders_count){
        summon_rider_index = 0;
      }
    }else if(prev_sw_ignite == OFF && sw_ignite == ON){
      change_state(STATE_FINISH_READY);
      state_start_point_ms = now_ms;
    }else if(prev_sw_read == ON && sw_read == OFF){
      change_state(STATE_INIT_2);
    }else if(prev_sw_read == OFF && sw_read == ON){
      change_state(STATE_READ_READY);
      summon_riders_count = 0;
    }
    break;
  case STATE_FINISH_READY:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_SUMMON_1);
    }
    break;
  default:
    ;
  }

  ////////////////// 時間経過状態遷移管理 //////////////////

  if(state == STATE_BLAST_READY && now_ms - state_start_point_ms >= STATE_READY_WAIT_MS){
    change_state(STATE_INIT_2);
  }else if(state == STATE_FINISH_READY && now_ms - state_start_point_ms >= STATE_READY_WAIT_MS){
    change_state(STATE_SUMMON_2);
  }

  ////////////////// カード読取処理(状態遷移管理) //////////////////

  if(prev_sw_read == OFF && sw_read == ON){
    // 読み取り開始(※カード認識ボタンは押されっぱなしの想定)
    digitalWrite(PIN_QR_TRIG, LOW);
  }

  if(prev_sw_read == ON && sw_read == OFF){
    // 読み取り終了(3秒経過でも勝手に終わる)
    digitalWrite(PIN_QR_TRIG, HIGH);
  }

  if(digitalRead(PIN_QR_DLED) == HIGH){  // If read the QR code.
    uint8_t ch_index = 0;
    char read_code[7] = {0};
    uint8_t rider_index = 0;
    while (Serial2.available() > 0) {
      char ch = Serial2.read();
      if(ch == 0x0d) { // 終端文字
        Serial.println(read_code);

        while(rider_index < num_riders){
          // 完全一致は難しいので、読み取れた文字数分だけマッチさせる。
          // 4文字読めたら、後半の4文字でマッチさせる
          // 1文字しか読めなければ流石に諦める
          char comp_code[ch_index] = {0};
          if(ch_index <= 1){
            // 1文字しか読めていなければ、この時点で失敗にする
            change_state(STATE_READ_FAIL);
            break;
          }else{
            for(uint8_t i=0;i<=ch_index;i++){
              comp_code[i] = legend_riders[rider_index].code[6-ch_index+i];
            }
          }

          if(memcmp(comp_code, read_code, ch_index) == 0){
            if(summon_riders_count < 4){
              // 4人目までは順に格納
              summon_riders[summon_riders_count] = legend_riders[rider_index];
              summon_riders_count++;
            }else{
              // 5人目以降を読み込んだ場合は,最後の4人を有効にするため、順に前にずらす
              for(uint8_t i=0;i<3;i++){
                summon_riders[i] = summon_riders[i+1];
              }
              summon_riders[3] = legend_riders[rider_index];
            }
            // 読み取り成功
            change_state(STATE_SUMMON_READY);
            break;
          }
          rider_index++;
        }

        if(rider_index == num_riders){
          // 一つもマッチしなかったら読み取り失敗
          change_state(STATE_READ_FAIL);
        }

      }else{
        read_code[ch_index] = ch;
        ch_index++;
      }

      // これを入れておく方が正確にコードを認識できる(多分、ディレイを入れないと早過ぎて文字を取りこぼす)
      delay(10);
    }
  }

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

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

  prev_sw_trigger = sw_trigger;
  prev_sw_ignite  = sw_ignite;
  prev_sw_read    = sw_read;

  prev_state = state;

  delay(1);
}