レジェンド版 変身音叉・音角 ver.2.0をつくる

今回は『レジェンド変身ベルトシリーズ 変身音叉・音角』の強化改造になります。

開発経緯

古くから私の作品を見てくださっている方ならご存知かもしれませんが、音角の改造は既に一度やっています

『響鬼』放映当時に販売されていた『DX変身音叉・音角』をベースに、主にディスクアニマル対応機能を強化したものになっています。これはこれで一つの完成形と自分では思っていて、特に機械学習技術を取り入れてディスクアニマルを個別認識させたのは自分でもよくやったなと思っています。自分の作ったものの中ではかなりお気に入りの一品です。

さて、今回まさかの18年ぶりぐらいで新たに制作された『レジェンド変身ベルトシリーズ 変身音叉・音角』ですが、これがとても良い。玩具としてのギミックはかなり少ないものの、その造形の良さから、多くの響鬼ファンから好評を得ているように思われます(←自分がXを見ている限り)。自分としても素晴らしいと思っていて、『これを前に作った音角ぐらいに強化すれば、最強の音角ができるのでは??』と思うようになりました。

とはいえ、全く同じギミックを作るのは面白くないし、それ以前にそもそも同じ作り方ではおそらくできないだろうというのは見えていた(←内部スペースがかなり小さくなっている)ので、別のやり方で、前作と同等の機能は確保しつつ、さらに強化できるところはないかと考えながら作成していきました。

特徴

 

レジェンド版ではボタン操作で変身待機に入りましたが、DX版同様に、叩いて変身待機に入るようにしています。その後、ボタン操作で変身完了するのはレジェンド版・DX版と同様です。

レジェンド版ではDX版になかった機能として、ボタンの短押し/長押しで二種の変身音が切り替えられるようになっています。今回の改造ではその二種に加え、変身後または変身中にBGMが鳴るパターンを三種類用意しました。変身待機に入る前にボタンを長押し操作することで切り替えられます。その気になればバリエーションはいくらでも増やせますが、厳選してBGMは以下の3つにしました。

  1. 装甲
  2. 勇者見参

最後の「勇者見参」は、ヒビキさんというよりは、最終回で変身した京介をイメージしました。

なお、レジェンド版では変身待機音も二種鳴るようになっていますが、今回の作品では一種(コーラスあり)に絞っています。

逆にDX版にはあってレジェンド版にはなくなった機能として、鬼の目の発光と、ディスクアニマルの(擬似)読み取り機能があります。このうち、前者については劇中でほぼ描写がない(と、自分では思っている)機能なので、今回は非搭載としました。後者については、DX版の改造時と同様、玩具オリジナル機能からさらに強化する形で搭載しました(※玩具オリジナルは、ディスクアニマルが手動で回転はするものの、ディスクアニマルの鼓動?のような、これまた劇中で覚えのない音が鳴る)。

 

まず、音角をディスクアニマルに近づけると、ディスクアニマルを認識します。このとき、劇中でディスクアニマルが探索から帰ってきたときに音角が振動してお知らせするときの音が鳴ります。

認識後にディスクアニマルを叩くと、ディスクアニマルの起動音が鳴ります。共通の起動音の後、それぞれ個別の鳴き声が鳴ります。DX版改造のときは起動音までだったので、ここは前作からの進化ポイントです。

 

ディスクアニマルを音角にセットし、手で回転させてからボタンを押すと、読み取り音が鳴ると共に、再度ボタンを押すまでディスクアニマルが自動で回り続けます。DX改造のときはディスクアニマルの回転は手動だったので、ここも進化ポイントです。より劇中に近い動作になりました。

 

ディスクアニマル認識後にボタンを長押しすることで、変身時と同様、BGM付きの動作に切り替えることができます。自分はこれを「大規模索敵モード」と呼んでいますが、この状態でディスクアニマルを叩くと、劇中の大規模索敵をイメージして、大量のディスクアニマルが起動するような音声が鳴ります。BGM付き。

 

さらに、この状態でディスクアニマルの読み取りを行うと、こちらもBGM付きの読み取り音が鳴ります。これで、より劇中の印象に近いなりきり遊びができるようになったと思います。

ハードウェア解説

ここからは中身の解説です。まず具材から。

何はともあれレジェンド版音角です。ちなみにいつ買えなくなるからわからないので、私は普段使い用・改造用・バックアップ兼保存用で3本買いました。

今回使用したマイコンです。無線機能とかはいらないのでこれで充分です。サイズが小さいのも今回は好都合です。

今回はそれなりに音声データが多いのと音角の内部スペースが限られているので、音声再生モジュールはDFPlayer一択です。なお、スピーカーは、いつも使っているものよりワンサイズ小さめのものを使いました。前に電子部品屋さんで買っていたもので、メーカーは不明です。

DX版と同様の「叩いて変身」を実現するためのセンサです。DX版を改造したときの余りを使用しました。

ディスクアニマルの個別認識に使用するリーダーです。商品名からは気づきにくいですが、NFCタグが読めます。NFCタグは以前の作品作りで余った以下を使用しました。

どちらもサイズは同じです。

ディスクアニマルの読み取り(風)動作に使用。スペースの関係で、なるべく厚みのないものを選んでいます。

モーターの駆動に使用。もうちょい小型のドライバでも充分なのですが、手元にあったのがこれだったのでこれを使用しました。

安全性の観点から基本的には使いたくないものですが、今回は内部スペース的に使わざるを得ませんでした。

 

電子回路として主に必要な部品はこんな感じです。あとは電源ON/OFF用のスライドスイッチとか配線用の導線とかです。

ここからは電子回路とかではなく、造形面での改修に必要な具材です。レジェンド変身ベルトシリーズが、その商品コンセプト上、色々割り切られている関係で、レジェンド音角も、音叉部分がガッツリ肉抜きされていたり、塗装が大きく省略されたりしています(※それを差し置いても素の造形が素晴らしい)。ということで、いくら中を作り込んでも、外見がそのままだと、「中はスゴイけどあとは外側が…」みたいな評価になるのは目に見えているので、ここは苦手でもやらないわけにはいきません。以下、このあたりの初心者が使用した具材ということで、予めご了承ください。

ガッツリ開いてる肉抜き穴を埋めるのに使用しました。音角の穴を埋めるぐらいなら一箱で充分です。

こういうヘラもある方が作業的には捗ると思います。

私が初心者なだけかもしれませんが、エポキシパテだけで表面まで綺麗に仕上げるのは難しかったです。ということで、細かな凹凸のならしのために普通のパテも使用しました。うすめ液とあわせて溶きパテとして使用しました。

あと、パテで盛り付けた部分を綺麗にするのに耐水ペーパーを使用しています。100均とかで売ってる耐水ペーパーセットとかで充分です。

色合いとは拘ればキリがありませんが、私はざっくり合っていれば充分派ですので、普通にスプレー缶のゴールドとシルバーを買ってきました。

黒い部分の塗装はこちらを使用。

最後のドレスアップとして、塗装後に銀や黒のネジだと目立つところをこれと交換しました。

あとは、電子回路でも造形でもないところの具材としてネオジム磁石とOリングを使っていますが、これは手持ちのものを使用しました。

 

使用した具材はほぼほぼこれで全部です。ここからは具体的な改造ポイントの説明です。まず音叉部分。

とりあえず本来は存在しない先端の繋ぎ部分をカット。

続いて肉抜き部分の穴埋めです。エポキシパテで埋めていきます。軸受けパーツも少し肉抜きがあるので忘れずに。

とりあえずエポキシパテが乾燥した状態。ここから、600→800→1000番ぐらいでヤスリがけ。

結構綺麗にできた気はしますが、細かい傷みたいなものが残ってしまっています。

ということで、薄めたパテを盛り付けて、ここから再度600→800→1000番ぐらいでヤスリがけ。

最終、こんな感じになりました。

見た目はマダラですが、触った感じはとてもスベスベです。

音叉部分のその他改造ポイントとして、軸部分にOリングをいくつか巻いて、可動をわざと渋くしています。

というのも、デフォルトのままだと、ディスクアニマルの読み取り遊び(回転)のときに音叉がディスクアニマルに干渉するリスクが高いためです。そこで、音叉の可動を渋くすることで、音叉の位置を自由に決めてディスクアニマルとの干渉を防ぐようにしています。これをすると、手首のスナップ動作でカッコよく音叉部分を展開する、といった遊びはできなくなりますが、今回はディスクアニマル遊びのときの遊びやすさを優先したく、この仕様にしました。

続いて、ディスクアニマルのセット部分。ここは、元々の製品だとディスクアニマルがセットできない(軸が太くて入らない)ので、丸々作り直しになります。

ということで、丸ごとカットです。他にも、部品を入れるスペースの確保のため、内部を可能な限りカットしています。

 

本体加工はこんな感じで、次は塗装です。ここは本当に苦手です。とりあえず、まずは全部品にサーフェイサー。

続いて、ゴールドのラッカースプレーで塗装。

続いて、マスキングして音叉部分だけシルバースプレーで塗装。

鬼の顔部分のマスキングは大変なので、黒いところはエナメル塗料で筆塗りです。かばやんさんのやり方を参考にしました。

最後に全体につや消しスプレーを振って完成。

鬼の顔の墨入れは、私の腕でやると変な感じになるリスクの方が高そうだったのでやめました。

 

ここからは電子部品の仕込みになりますが、その前にディスクアニマル側の仕組みについて説明しておいた方が良さそうです。

前作『DX変身音叉・音角 Ver.2.0』のときは、玩具のディスクアニマルを無改造で認識して遊びができる、というのが大きなポイントでした。ただ、このやり方には以下の2つの弱点がありました。

  1. 個別認識がやや不安定
  2. 読み取り遊び時の固定が不安定

1. は、ディスクアニマルの色を色センサで読み取って識別する、ということをしていたので、似た色のディスクは認識を間違ったり、またディスクを置いていなくても、センサの上に何かあればディスクとして色を読もうとしてしまう、という誤動作がありました。

2. は、素のディスクアニマルは、ディスク形態を維持できるように軽いロックはかかっているものの、強く回転させるとロックが外れてディスク形態が歪んでしまうため、あまり強く回転させることができない、というものです。とくに鈍色蛇が絶望的です。

今回、前作と同じやり方で作ってしまうと上記1., 2.がそのまま課題として残ってしまうので、今回は別のアプローチを試すことにしました。

で、そのために用意したのが右のトレイです。3Dプリンタで作成しました。

トレイを使用することで、「玩具のディスクアニマルをそのままセットして遊べる」という優位性はなくなってしまいますが、代わりに1., 2.の問題を解消することができます。

まず2.は自明で、トレイがあることでディスクが水平を保ちやすくなるため、ディスクが回転中に展開し始めるリスクを大きく減らすことができます。過去のDX玩具版でもこの目的でトレイがついていましたが、側面までロックするしっかりしたものでした。それだと流石に見栄えが良くないので、今回作成したトレイは底面のみを保持する形のものにしています。保持力は劣りますが、今回は回転の強さは電子的に制御できるようになっているので、このトレイで問題ないレベルに設定すれば問題なしです。

次に1.の解消ですが、トレイの採用により、NFCタグをトレイに仕込むことができるようになります。つまり、ディスクアニマルと一緒にNFCタグを交換するようにすれば、ディスクアニマルを無改造のまま確実な個別認識ができる、というわけです。前作のやり方と比べるとちょっとズルっぽいやり方ですけれど。

なお、トレイを使わずに、各ディスクアニマルを分解してそれぞれの中にNFCタグを仕込む、というやり方も考えられます。ただ、それができるサイズのNFCタグとなると、市場的にはめずらしくなって値が張るようになってきますので、今回は見送りました。

で、こちらが今回用意したNFCタグです。全9種類。せっかくなので、ミニディスクアニマル風にしてみました。

 

と、長くなりましたが、以上がディスクアニマル側の仕込みになります。これを踏まえて、音角側の部品の配置を考えます。

まずNFCタグを読み取るためのリーダーですが、サイズ的に本体中心部に置く以外の選択肢がありません。

元の部品のままだとサイズオーバーなので、ケースを分解して中身だけ使用します。

続いて今回の作品の肝である、ディスクアニマルの読み取りアクション(回転)部分です。先に述べたとおりここは新規に作成していて、まず軸パーツを3Dプリンタで作成しています。で、軸パーツをモーターに直接挿して回転させます。

軸パーツの回転をディスクアニマル(をセットしたトレイ)に伝えようと思うと、素直に考えるなら軸をトレイをしっかり密着させる必要があるのですが、それによってディスクの着脱でモタモタしてしまうのはイマイチだし壊れやすくもなってしまいます。

ということで、ネオジム磁石で固定することにしました。あんまり回転を強くすると固定できなくなりそうな気がしますが、今回ぐらいの回転なら問題ありません。軸そのものに磁石を仕込むのは難しいので、水平方向に磁石を配置するため、軸周辺含めて回転する形に軸パーツを作りました。トレイ側にも対応するように薄型ネオジム磁石を配置しています。

なお、軸パーツをつけたモーターですが、音角の中でグラグラしないよう、写真のような治具パーツを3Dプリンタで作って円周の厚みを増やした上で本体に収めています。そして、この治具パーツの底面にはスピーカーを収納しています。この形でスピーカーとモーターを収めたかったので、モーターは薄型のものを使用しました。

ちなみに、この軸は元々の音角の軸と比べるとだいぶ細くなってしまっているので、気になる人向けに、太らせる用のキャップも用意しました。

やはり太い方が見慣れた感じがして良いです。

 

あとは配線して中に収めるのみです。回路図はこんな感じです。

配線時、ボタン部分の配線はちょっと工夫しています。新しいスイッチを別から持ってくると結構固定とか難しかったりするので、レジェンド版音角に元々入っていた基板を使います。

基板の下半分を切り取って、

上記の部分に配線すれば、レジェンド版音角と同じようにボタンが使えるようになります。

で、全部の部品を実際に収めた結果がこちらになります。

なんとか収まってくれました。

最後に、ネジをいくつか金色のものに交換して、本体としては完成です。

おつかれさまでした。

ソフトウェア解説

ソフトの解説はハードに比べるとずっと簡単です。状態遷移図はこんな感じになります。

できるだけボタンを増やさない形で各機能を実現できるように考えてみました。ソースコードの全文については、このブログ記事の最後に掲載します。

プログラムについて今回少し気をつけたのは、振動センサの扱いと、ディスクアニマルを回すためのモーター制御です。

振動センサについては単純な話で、loop関数の中でのdelayを長く設定してしまうと感度がどんどん悪くなります。センサの中のバネが芯線に接触するのは一瞬なので、プログラムに待ちが入るとその一瞬の接触が見逃されてしまいます。ということで、今回はdelayは2ms程度にしました。

続いてモーター制御です。モーターの回転速度はPWM制御で調整していますが、何も考えずにanalogWriteを使うと、モーターからそこそこ大きな音で「キーン」みたいな音が鳴ってしまいました。どうやらPWMの周波数が人の可聴領域に入ってしまっている模様。

通常なら気にしないところなのですが、ディスクアニマルの読み取り音を鳴らしていても気になるぐらいにはうるさかったので、PWMの周波数を変更することにしました。通常のArduinoだとまた別のやり方になるかもしれませんが、今回使用したSeeeduino XIAOだとそのものズバリでpwm関数が使えたので、これで周波数を人の可聴領域外である25,000Hzまで上げました。

まとめ

以上、レジェンド版 変身音叉・音角 ver.2.0のご紹介でした。前作は前作で気に入っていましたが、今回、自分としては決定版と言える音角ができたかなと思っています。レジェンド版を出してくれてありがとうバンダイ様。

さて、いよいよ来年は響鬼が20周年を迎えます。CSMが出るとしたらここしかないでしょう。是非、これを超えるクオリティの音角を出してもらえればと思っています。

 

ソースコード

ソースコードの全文はこちらになります。なお、このコードを実行するには、同じフォルダに、こちらにある”MFRC522_I2C.cpp”と”MFRC522_I2C.h”を配置する必要があります。NFCリーダーを動作させるためのライブラリになります。

#define PIN_MP3_RX  0
#define PIN_MP3_TX  1
#define PIN_MOTOR_1 2
#define PIN_MOTOR_2 3
// Seeeduino XIAOのSDAはピン4、SCLはピン5で固定
#define PIN_SW      6
#define PIN_VIB     7

#define ON  LOW
#define OFF HIGH

uint8_t sw = OFF;
uint8_t prev_sw = OFF;
uint8_t vib = OFF;
uint8_t prev_vib = OFF;

#define STATE_INIT_1              0
#define STATE_INIT_2              1
#define STATE_READY               2
#define STATE_CHANGING            3
#define STATE_CHANGE              4
#define STATE_DA_INIT             5
#define STATE_DA_ACTIVATE         6
#define STATE_DA_READING          7
#define STATE_DA_SEARCH_INIT      8
#define STATE_DA_SEARCH_ACTIVATE  9
#define STATE_DA_SEARCH_READING  10

uint8_t state      = STATE_INIT_1;
uint8_t prev_state = STATE_INIT_1;

#define STATE_CHANGING_MS           10000
#define STATE_CHANGE_MS              5000
#define STATE_DA_ACTIVATE_MS         5000
#define STATE_DA_SEARCH_ACTIVATE_MS  5000
unsigned long state_change_point_ms = 0;

void change_state(uint8_t new_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_READY:              Serial.println(F("READY")); break;
  case STATE_CHANGING:           Serial.println(F("CHANGING")); break;
  case STATE_CHANGE:             Serial.println(F("CHANGE")); break;
  case STATE_DA_INIT:            Serial.println(F("DA_INIT")); break;
  case STATE_DA_ACTIVATE:        Serial.println(F("DA_ACTIVATE")); break;
  case STATE_DA_READING:         Serial.println(F("DA_READING")); break;
  case STATE_DA_SEARCH_INIT:     Serial.println(F("DA_SEARCH_INIT")); break;
  case STATE_DA_SEARCH_ACTIVATE: Serial.println(F("DA_SEARCH_ACTIVATE")); break;
  case STATE_DA_SEARCH_READING:  Serial.println(F("DA_SEARCH_READING")); break;
  default: ;
  }
}

#define SW_THRESHOLD_MS 900
unsigned long sw_pressed_point_ms = 0;
boolean is_sw_pressed = false;

#define SOUND_POWER_ON          1
#define SOUND_SETTING_1         2
#define SOUND_SETTING_2         3
#define SOUND_CHANGING          4
#define SOUND_CHANGE_BGM_NONE_1 5
#define SOUND_CHANGE_BGM_NONE_2 6
#define SOUND_CHANGE_BGM_1      7
#define SOUND_CHANGE_BGM_2      8
#define SOUND_CHANGE_BGM_3      9
#define SOUND_DA_DETECT        10
#define SOUND_DA_ACTIVATE      11
#define SOUND_DA_READING       12
#define SOUND_DA_READING_BGM   13
#define SOUND_DA_AKANETAKA     14
#define SOUND_DA_RURIOKAMI     15
#define SOUND_DA_RYOKUOZARU    16
#define SOUND_DA_KIHADAGANI    17
#define SOUND_DA_NIBIIROHEBI   18
#define SOUND_DA_KIAKASHISHI   19
#define SOUND_DA_ASAGIWASHI    20
#define SOUND_DA_SEIJIGAERU    21
#define SOUND_DA_KOGANEOKAMI   22
#define SOUND_DA_SEARCH_AKANETAKA    23
#define SOUND_DA_SEARCH_RURIOKAMI    24
#define SOUND_DA_SEARCH_RYOKUOZARU   25
#define SOUND_DA_SEARCH_KIHADAGANI   26
#define SOUND_DA_SEARCH_NIBIIROHEBI  27
#define SOUND_DA_SEARCH_KIAKASHISHI  28
#define SOUND_DA_SEARCH_ASAGIWASHI   29
#define SOUND_DA_SEARCH_SEIJIGAERU   30
#define SOUND_DA_SEARCH_KOGANEOKAMI  31

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

#include "MFRC522_I2C.h"

uint8_t da_index = 0;

MFRC522 mfrc522(0x28);

#define DISK_ANIMAL_COUNTS 9

typedef struct {
  byte tag_id[7];
  uint8_t voice_id;
  uint8_t search_sound_id;
} disk_animal;

disk_animal disk_animals[] = {
  {{0x04, 0x55, 0x0B, 0xAA, 0x7B, 0x2B, 0x84}, SOUND_DA_AKANETAKA,   SOUND_DA_SEARCH_AKANETAKA},
  {{0x04, 0x72, 0x0B, 0xAA, 0x7B, 0x2B, 0x84}, SOUND_DA_RURIOKAMI,   SOUND_DA_SEARCH_RURIOKAMI},
  {{0x04, 0x8F, 0x0B, 0xAA, 0x7B, 0x2B, 0x84}, SOUND_DA_RYOKUOZARU,  SOUND_DA_SEARCH_RYOKUOZARU},
  {{0x04, 0x90, 0x0B, 0xAA, 0x7B, 0x2B, 0x84}, SOUND_DA_KIHADAGANI,  SOUND_DA_SEARCH_KIHADAGANI},
  {{0x04, 0x73, 0x18, 0xB2, 0x7B, 0x2B, 0x80}, SOUND_DA_NIBIIROHEBI, SOUND_DA_SEARCH_NIBIIROHEBI},
  {{0x04, 0x29, 0xD1, 0x78, 0xB6, 0x2A, 0x81}, SOUND_DA_KIAKASHISHI, SOUND_DA_SEARCH_KIAKASHISHI},
  {{0x04, 0x16, 0xD1, 0x78, 0xB6, 0x2A, 0x81}, SOUND_DA_ASAGIWASHI,  SOUND_DA_SEARCH_ASAGIWASHI},
  {{0x04, 0x92, 0x18, 0xB2, 0x7B, 0x2B, 0x80}, SOUND_DA_SEIJIGAERU,  SOUND_DA_SEARCH_SEIJIGAERU},
  {{0x04, 0xAC, 0x0B, 0xAA, 0x7B, 0x2B, 0x84}, SOUND_DA_KOGANEOKAMI, SOUND_DA_SEARCH_KOGANEOKAMI}
};

byte current_tag_id[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

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

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

#define VOLUME 18 // 0〜30

SoftwareSerial ss_mp3_player(PIN_MP3_RX, PIN_MP3_TX);
DFPlayerMini_Fast mp3_player;

#define CHANGE_SOUND_COUNTS 5
uint8_t change_sound_index = 0;

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

void play_sound(uint8_t bgm_num){
  mp3_player.playFromMP3Folder(bgm_num);
  Serial.print(F("Play BGM: "));
  Serial.println(bgm_num);
}

void control_sound(){
  switch(prev_state){
  case STATE_INIT_1:
    if(state == STATE_INIT_2){
      change_sound_index++;
      if(change_sound_index >= CHANGE_SOUND_COUNTS){
        change_sound_index = 0;
      }
      if(change_sound_index == 0){
        play_sound(SOUND_SETTING_2);
      }else{
        play_sound(SOUND_SETTING_1);
      }
    }else if(state == STATE_READY){
      pause_sound();
    }else if(state == STATE_DA_INIT){
      play_sound(SOUND_DA_DETECT);
    }
    break;
  case STATE_INIT_2:
    if(state == STATE_INIT_1){
      change_sound_index++;
      if(change_sound_index >= CHANGE_SOUND_COUNTS){
        change_sound_index = 0;
      }
      if(change_sound_index == 0){
        play_sound(SOUND_SETTING_2);
      }else{
        play_sound(SOUND_SETTING_1);
      }
    }else if(state == STATE_READY){
      pause_sound();
    }else if(state == STATE_DA_INIT){
      play_sound(SOUND_DA_DETECT);
    }
    break;
  case STATE_READY:
    if(state == STATE_CHANGING){
      play_sound(SOUND_CHANGING);
    }
    break;
  case STATE_CHANGING:
    if(state == STATE_CHANGE){
      switch(change_sound_index){
      case 0: play_sound(SOUND_CHANGE_BGM_NONE_1); break;
      case 1: play_sound(SOUND_CHANGE_BGM_NONE_2); break;
      case 2: play_sound(SOUND_CHANGE_BGM_1); break;
      case 3: play_sound(SOUND_CHANGE_BGM_2); break;
      case 4: play_sound(SOUND_CHANGE_BGM_3); break;
      default: ;
      }
    }else if(state == STATE_INIT_1){
      change_sound_index = 0;
    }
    break;
  case STATE_CHANGE:
    if(state == STATE_INIT_1){
      pause_sound();
      change_sound_index = 0;
    }
    break;
  case STATE_DA_INIT:
    if(state == STATE_DA_ACTIVATE){
      play_sound(SOUND_DA_ACTIVATE);
    }else if(state == STATE_DA_READING){
      play_sound(SOUND_DA_READING);
    }else if(state == STATE_DA_SEARCH_INIT){
      play_sound(SOUND_SETTING_1);
    }
    break;
  case STATE_DA_ACTIVATE:
    if(state == STATE_DA_INIT){
      play_sound(disk_animals[da_index].voice_id);
    }
    break;
  case STATE_DA_READING:
    if(state == STATE_DA_INIT){
      pause_sound();
    }
    break;
  case STATE_DA_SEARCH_INIT:
    if(state == STATE_DA_SEARCH_ACTIVATE){
      play_sound(disk_animals[da_index].search_sound_id);
    }else if(state == STATE_DA_SEARCH_READING){
      play_sound(SOUND_DA_READING_BGM);
    }else if(state == STATE_DA_INIT){
      play_sound(SOUND_DA_DETECT);
    }else if(state == STATE_INIT_1){
      play_sound(SOUND_SETTING_2);
      change_sound_index = 0;
    }
    break;
  case STATE_DA_SEARCH_ACTIVATE:
    break;
  case STATE_DA_SEARCH_READING:
    if(state == STATE_DA_SEARCH_INIT){
      pause_sound();
    }
    break;
  default:
    ;
  }
}

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

#define DISK_SPEED_INIT    120
#define DISK_SPEED_STABLE  50 // INITとSTABLEは異なる値にする必要あり

#define PWM_FREQ 25000 // デフォルトは1000Hz? モーターの音を減らすため、可聴範囲外の周波数に設定
#define PWM_DUTY_INIT   716 // これでおそらく70%
#define PWM_DUTY_STABLE 667 // これでおそらく65%。INITとSTABLEを変更させないときも、値は別にする

uint16_t disk_speed = 0;

unsigned long disk_read_point_ms = 0;
#define DISK_READ_INIT_MS 3000

void control_motor(unsigned long now_ms){
  if(prev_state == STATE_DA_INIT && state == STATE_DA_READING){
    disk_speed = PWM_DUTY_INIT;
    disk_read_point_ms = now_ms;
    pwm(PIN_MOTOR_1, PWM_FREQ, disk_speed);
  }else if(prev_state == STATE_DA_READING && state == STATE_DA_INIT){
    //pwm(PIN_MOTOR_2, PWM_FREQ, disk_speed); // ブレーキ
    analogWrite(PIN_MOTOR_1, 0); // 自然停止
  }else if(prev_state == STATE_DA_INIT && state != STATE_DA_READING){
    analogWrite(PIN_MOTOR_1, 0);
    analogWrite(PIN_MOTOR_2, 0);
  }else if(prev_state == STATE_DA_SEARCH_INIT && state == STATE_DA_SEARCH_READING){
    disk_speed = PWM_DUTY_INIT;
    disk_read_point_ms = now_ms;
    pwm(PIN_MOTOR_1, PWM_FREQ, disk_speed);
  }else if(prev_state == STATE_DA_SEARCH_READING && state == STATE_DA_SEARCH_INIT){
    //pwm(PIN_MOTOR_2, PWM_FREQ, disk_speed); // ブレーキ
    analogWrite(PIN_MOTOR_1, 0); // 自然停止
  }else if(prev_state == STATE_DA_SEARCH_INIT && state != STATE_DA_SEARCH_READING){
    analogWrite(PIN_MOTOR_1, 0);
    analogWrite(PIN_MOTOR_2, 0);
  }

  if(disk_speed == PWM_DUTY_INIT && now_ms - disk_read_point_ms > DISK_READ_INIT_MS){
    disk_speed = PWM_DUTY_STABLE;
    pwm(PIN_MOTOR_1, PWM_FREQ, disk_speed);
  }

}

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

void setup(void){
  Serial.begin(115200);
  pinMode(PIN_MOTOR_1, OUTPUT);
  pinMode(PIN_MOTOR_2, OUTPUT);
  pinMode(PIN_SW, INPUT_PULLUP);
  pinMode(PIN_VIB, INPUT_PULLUP);

  Wire.begin();
  mfrc522.PCD_Init();

  // 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(100);
  mp3_player.volume(VOLUME);

  delay(100);
  play_sound(SOUND_POWER_ON);
}

void loop(){
  sw  = digitalRead(PIN_SW);
  vib = digitalRead(PIN_VIB);

  unsigned long now_ms = millis();

  // ボタン処理
  if(prev_sw == OFF && sw == ON){
    // 押されたとき
    is_sw_pressed = true;
    sw_pressed_point_ms = now_ms;
    state_change_point_ms = now_ms;
  }

  if(prev_sw == ON && sw == OFF){
    // 離されたとき
    if(now_ms - sw_pressed_point_ms < SW_THRESHOLD_MS){
      // 短押し処理(※ 長押し処理は時間経過の方と併せて記述)
      switch(state){
      case STATE_INIT_1:
      case STATE_INIT_2:
        change_state(STATE_READY);
        break;
      case STATE_CHANGING:
        change_state(STATE_CHANGE);
        break;
      case STATE_CHANGE:
        change_state(STATE_INIT_1);
        break;
      case STATE_DA_INIT:
        change_state(STATE_DA_READING);
        break;
      case STATE_DA_READING:
        change_state(STATE_DA_INIT);
        break;
      case STATE_DA_SEARCH_INIT:
        change_state(STATE_DA_SEARCH_READING);
        break;
      case STATE_DA_SEARCH_READING:
        change_state(STATE_DA_SEARCH_INIT);
        break;
      default:
        ;
      }
    }
    is_sw_pressed = false;
  }

  // 振動検知処理
  if(prev_vib == OFF && vib == ON){
    switch(state){
    case STATE_READY:
      change_state(STATE_CHANGING);
      state_change_point_ms = now_ms;
      break;
    case STATE_DA_INIT:
      change_state(STATE_DA_ACTIVATE);
      state_change_point_ms = now_ms;
      break;
    case STATE_DA_SEARCH_INIT:
      change_state(STATE_DA_SEARCH_ACTIVATE);
      state_change_point_ms = now_ms;
      break;
    default:
      ;
    }
  }

  // NFCタグ検出処理
  if(mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()){
    // NFCタグがあり、かつ、そのUIDが読めた
    for(uint8_t i=0; i<mfrc522.uid.size; i++) {  // Output the stored UID data.
      Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
      Serial.print(mfrc522.uid.uidByte[i], HEX);
    }
    Serial.println("");

    if(state == STATE_INIT_1 || state == STATE_INIT_2 || state == STATE_DA_SEARCH_INIT){
      for(uint8_t i=0; i<DISK_ANIMAL_COUNTS; i++){
        if(memcmp(disk_animals[i].tag_id, mfrc522.uid.uidByte, mfrc522.uid.size) == 0){
          da_index = i;
          memcpy(current_tag_id, mfrc522.uid.uidByte, mfrc522.uid.size);
          break;
        }
      }
      change_state(STATE_DA_INIT);
    }else if(state == STATE_DA_INIT){
      if(memcmp(current_tag_id, mfrc522.uid.uidByte, mfrc522.uid.size) != 0){
        // 読み込み済みのNFCタグと異なるタグを読み込んだ場合、一度INIT_1に戻してから再度DA_INITに遷移させる
        change_state(STATE_INIT_1);
      }
    }
  }

  // 時間経過処理
  switch(state){
  case STATE_INIT_1:
    if(is_sw_pressed && now_ms - state_change_point_ms > SW_THRESHOLD_MS){
      change_state(STATE_INIT_2);
      state_change_point_ms = now_ms;
    }
    break;
  case STATE_INIT_2:
    if(is_sw_pressed && now_ms - state_change_point_ms > SW_THRESHOLD_MS){
      change_state(STATE_INIT_1);
      state_change_point_ms = now_ms;
    }
    break;
  case STATE_CHANGING:
    if(now_ms - state_change_point_ms > STATE_CHANGING_MS){
      change_state(STATE_INIT_1);
    }
    break;
  case STATE_DA_INIT:
    if(is_sw_pressed && now_ms - state_change_point_ms > SW_THRESHOLD_MS){
      change_state(STATE_DA_SEARCH_INIT);
      state_change_point_ms = now_ms;
    }
    break;
  case STATE_DA_ACTIVATE:
    if(now_ms - state_change_point_ms > STATE_DA_ACTIVATE_MS){
      change_state(STATE_DA_INIT);
    }
    break;
  case STATE_DA_SEARCH_INIT:
    if(is_sw_pressed && now_ms - state_change_point_ms > SW_THRESHOLD_MS){
      change_state(STATE_INIT_1);
      state_change_point_ms = now_ms;
    }
    break;
  case STATE_DA_SEARCH_ACTIVATE:
    if(now_ms - state_change_point_ms > STATE_DA_SEARCH_ACTIVATE_MS){
      change_state(STATE_DA_SEARCH_INIT);
    }
    break;
  default:
    ;
  }

  // 音声制御
  control_sound();

  // モーター制御
  control_motor(now_ms);

  prev_sw = sw;
  prev_vib = vib;
  prev_state = state;

  // 振動センサの感度が鈍るので、delayは長くても10ms以内にする
  delay(2);
}