『大恐竜時代へGO!! GO!!』のギズモを作ってみた

Ken published on
5 min, 962 words

Categories: gadget

大恐竜時代へGO!! GO!!に登場するギズモを作ってみた。

最終的にはリストバンドにマジックテープを縫い付けて、腕に装着できるようにした。
ギズモ

今回使ったデバイスは M5Stack Basic V2.6 という製品。
https://www.switch-science.com/catalog/7362/

続きは作る過程の話。

素材の準備

動画の準備

まずは画像と音を切り出す動画 ( full_movie.mp4 ) を用意する。
動画全体はいらないので、必要そうな場所だけを抜き出しておく。

ffmpeg -ss 1:23 -to 4:56 -i full_movie.mp4 -c copy movie.mp4

音の準備

次は音を取り出す。

ffmpeg -i movie.mp4 -vn -acodec copy bgm.mp3

音声だけmp3形式で取り出せるんだけど、M5Stackでちょうどよい感じの音質にまで劣化させておく。

ffmpeg -i bgm.mp3 -ar 8000 -ac 1 -acodec pcm_u8 bgm.wav

mp3wav のどっちがいいのかはわからない。
聴き比べてみたら wav の方が聴きやすかったから今回は wav を採用。

画像の準備

音の次は画像を用意する。
下のコマンドを実行すると、1秒間から10枚の画像を切り出してくれる。

ffmpeg -i movie.mp4 -r 10 -vcodec mjpeg image_%03d.jpg

音声も同様にコマンドで抜き出す。

ffmpeg -i movie.mp4 -vn -acodec copy bgm.mp3

組み込む

素材が揃ったところで M5Stack に組み込んでいくんだけど、素材はあらかじめSDカードに入れて M5Stack に刺しておく。

IDEの準備

今回は Visual Studio CodePlatformIO エクステンションを入れて作っていく。

設定手順は省くが、 platformio.ini はライブラリとバージョンの情報が役立ちそうなので載せておく。

[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
lib_deps = 
	m5stack/M5Stack@^0.3.9
	gianbacchio/ESP8266Spiram@^1.0
	earlephilhower/ESP8266Audio@1.9.3
upload_port = /dev/cu.wchusbserial52D2026711
monitor_port = /dev/cu.wchusbserial52D2026711
monitor_speed = 115200

ここで重要なのが ESP8266Audio のバージョン。
最新の 1.9.4 では音が鳴らず、 1.9.3 では音が鳴った。

ESP8266Audio@1.9.4 で表示されていたエラーはこんな感じ。

AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: 76797574
AudioGeneratorWAV::begin: failed during ReadWAVInfo

ライブラリの情報は PlatformIO で設定すれば反映されるが、下の3行は platformio.ini を直接追記した。

upload_port = /dev/cu.wchusbserial52D2026711
monitor_port = /dev/cu.wchusbserial52D2026711
monitor_speed = 115200

実装

実装準備が整ったので実装。

音を流したまま画像を切り替えようと、 wav->begin(file, out); の直後に画像表示を行うと音の再生が止まった。
画像表示が終わってから wav->isRunning() のあたりを実行すると引き続き流れるので、今回は画像表示 → 音再生 の順序にしている。

#include <M5Stack.h>
#include <WiFi.h>
#include "AudioFileSourceSD.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"

#define IMAGE_GIZMO "/gizmo.jpg"

void playWav(const char path[]){
	Serial.println("#playWav");
	AudioFileSourceSD *file = new AudioFileSourceSD(path);
	AudioOutputI2S *out = new AudioOutputI2S(0, 1);
	out->SetOutputModeMono(true);
	out->SetGain(0.6);

	AudioGeneratorWAV *wav = new AudioGeneratorWAV();
	wav->begin(file, out);
	while(wav->isRunning()){
		if (!wav->loop()) wav->stop();
	}
}

void drawJpeg(char* path) {
	M5.Lcd.drawJpgFile(SD, path);
}

void drawPlayWav(char* jpgPath, const char wavPath[]) {
	drawJpeg(jpgPath);
	playWav(wavPath);
}

void drawGizmo() {
	Serial.println("#drawGizmo");
	drawJpeg((char*)IMAGE_GIZMO);
}

void playCheck() {
	Serial.println("#playCheck");
	drawPlayWav((char*)"/check/01_check_vest.jpg", "/check/01_check_vest.wav");
	drawPlayWav((char*)"/check/02_check_gizmo.jpg", "/check/02_check_gizmo.wav");
	drawPlayWav((char*)"/check/03_check_hat.jpg", "/check/03_check_hat.wav");
	drawPlayWav((char*)"/check/04_check_bag.jpg", "/check/04_check_bag.wav");
}

void playGo() {
	Serial.println("#playCheck");
	drawPlayWav((char*)"/go/01_go.jpg", "/go/01_go.wav");
	drawPlayWav((char*)"/go/02_reverse.jpg", "/go/02_reverse.wav");
	drawPlayWav((char*)"/go/03_warp.jpg", "/go/03_warp.wav");
	drawPlayWav((char*)"/go/04_arrive.jpg", "/go/04_arrive.wav");
}

void playSmatt() {
	Serial.println("#SoftwareSerial");
	drawPlayWav((char*)"/smatt/smatt.jpg", "/smatt/smatt.wav");
}

void setup() {
	M5.begin();
	M5.Power.begin();
	M5.Lcd.setBrightness(15);
	WiFi.mode(WIFI_OFF); 
	delay(500);
	Serial.println("#setup");

	drawGizmo();
}

void loop() {
	M5.update();
	if (M5.BtnA.wasPressed()) {
		playCheck();

		delay(2000);
		drawGizmo();
	} else if (M5.BtnB.wasPressed()) {
		playGo();

		delay(2000);
		drawGizmo();
	} else if (M5.BtnC.wasPressed()) {
		playSmatt();
	}
}