Claude Codeで作るアプリをPC以外にも組み込んでいくプロジェクトを進めています。今回のターゲットはRISC-Vチップを積んだ5インチタブレットです。

10日ほど、故郷の長崎に帰省しています。目的は、中学校の同窓会でバンド演奏をするため。筆者だけが遠方にいるので、ちょっと前に入って残りのメンバーと練習をしておこうというわけです。
すでに2回、それぞれ3時間ずつ練習をし、このために実家にエレピ(以前買った折りたたみ88鍵)を導入し、個人練習もできるようにしています。
本番は10月25日なので、ちょいと時間があるのですが、無限に時間が溶けていく趣味を筆者は持っています。
主にClaude Codeを使ったVibe Codingです。
メインマシンは自宅のRTX 5090/4090/4080のWindowsマシン(2台はWSL2のUbuntuで動作)、DGX Spark互換機、M4 Max MacBook Pro(128GB)、M4 Mac miniで、これらにはVPNのTailscale経由でどこからでもアクセスできます。
今回、帰省につれてきたのは、普段は浅草橋のオフィスに置いているM2 MacBook Air。これで何をしようか思案しました。
最近は動画生成や音楽生成だけでなく、組み込みデバイスにも対象を広げ、Raspberry Pi 4を使ったAirDrop互換モジュールを作ったり、インドアバイクでどこでも走れるようにしたりローグバイクゲームを作ったりと、ハードウェア連動でやることが増えてきました。
先日、久しぶりに技研バーに行ったら、AI研究家の清水亮さんが、面白いものを見せてくれました。
独自開発のOSを、見知らぬ小型コンピュータで動かしているのです。
見慣れない筐体。Cardputerのようでもあるけど、ディスプレイがでかいし、キーボードもキー数が多い。
M5Stackの大型ディスプレイ。なんか見覚えがある。あ、Tab5だ。うちにある。それにキーボードを繋げているのです。
¥12,510
(価格・在庫状況は記事公開時点のものです)
Tab5は1万2000円ちょっとと安いので買ったんだけど、起動して出てくる密集した画面に恐れをなして、そのまま放置していたのでした。これを充電して長崎に持っていきました。
Tab5は、M5Stackが2025年に出した5インチのタブレット型開発ボードです。
SoCはEspressifのESP32-P4。RISC-Vのデュアルコアで最大400MHz、PSRAMが32MB、フラッシュが16MB。1280×720のIPSタッチパネルに、ES8388のオーディオコーデックと1Wのスピーカー、3.5mmのヘッドフォンジャック、USB-AホストとUSB-C、6軸IMUのBMI270、Wi-Fi 6とBluetoothを担当するESP32-C6まで載っています。
LinuxもAndroidも動きません。Arduino IDEかESP-IDFでC++を書いて焼き込む、純粋なマイコンの世界です。
筆者には、タッチパネルで動く、最適のプロジェクトがあります。ギターアプリのFretCasterです。
FretCasterをマイコンに載せる
iPadで動いている自作ギターアプリFretCasterは、弦とフレットをタッチして鳴らし、指を上下にずらしてチョーキング、本体を揺らしてビブラート、コードストリップ(長方形のボタン)でストロークという構成になっています。
音はTone.jsのようなライブラリではなく、Swiftで1サンプルずつ波形を計算する自前のエンジンです。
この自前エンジンというのが効きました。倍音を足して、ピックノイズを乗せて、歪ませる。SwiftからC++に書き直せば、そのままマイコンで動くはずです。
というわけで、長崎の実家でM2 MacBook AirにClaude Codeを立ち上げ、「M5StackのTab5を電子楽器として使いたい」と伝え、FretCasterのGitHubプライベートリポジトリを見せました。
Claude Codeは、まずTab5の仕様を調べ、Arduino CLIを入れようとしてHomebrewがXcodeのライセンス承諾で止まるとバイナリを直接落としてきて、M5Stackのボードパッケージとライブラリ(M5Unified)を入れ、FQBNを確認してビルドスクリプトを作りました。この間、筆者はTab5をUSBでつなぎます。つなぐ君です。
音を出すまで
エンジンの移植で変えたのは精度です。Swift版は倍精度のsin()を倍音ごとに呼び出していますが、ESP32-P4のFPUは単精度なので、2048エントリの正弦波テーブルを引く方式に変えました。サンプリングレートは48kHz、256サンプルずつ計算してM5Unifiedのスピーカー機能に渡します。
計算をコア0、タッチと描画をコア1に分け、256サンプル(5.3ms)の計算にかかる時間を測ると、無音時0.55ms、6音同時で2.5ms、10音で3.8ms。ギターのソロとコードを重ねるには十分です。
ここまでは順調でしたが、触ってみると、音が鳴ったり鳴らなかったり、押して数秒後に音が出る、といった症状が出ました。
Claude Codeがシリアルモニタをつないでいる間だけ反応がいい、という筆者の観察から、原因がUSBシリアルだということが判明。ESP32-P4のUSBシリアルは、Mac側がポートを読んでいないと送信がブロックし、ログを出すたびにUI側のループが止まっていたのです。送信タイムアウトをゼロにして、ログはデバッグモードのときだけ出すようにしたら、遅れは気にならなくなりました。
タッチパネルは100Hzで座標を返してきます。iPadの120Hzには及びませんが、チョーキングには十分なレスポンスが得られます。
ギタリストとしての注文
ここからはギター弾きとしての細かい注文を投げていきました。
「チョーキングは押さえているフレットの音から上方向にしか行かないはず」。指を上にずらしても下にずらしても、音程は上がる方向だけに変わるようにしました。実際のギターでは、押し上げても引き下げても弦は引っ張られるので音は上がります。これはiPad版ではちゃんと実装されていたのですが、デグレしてしまったようです。
「半音チョーキングがしにくい」。最初は60ピクセルで2音上がる設定でしたが、弦2本分ずらして1音上がる程度に緩めました。これも同様。
「同一弦でのスライドがチョーキングに誤認される」。横にスライドすると、どうしても縦方向に指がぶれます。そこで弦の幅の3割までの縦ぶれは無視し、フレットが変わるたびにチョーキングの基準点をリセットするようにしました。逆に80セント以上曲げたら、戻すまでフレットは変わりません。
フレットは1画面に10個、0~9、8~17、16~24の3段階を切り替える形にしました。5インチの画面でフレット幅を稼ぐには、これくらいが限界です。
この辺りは実機で試してみないとわからないところですね。
コードストリップは、iPad/iPhone版GarageBandの機能を改良した2段16パッドで、パッドの中央を叩くとダウンストローク、右端でアップ、左端からなぞるとアルペジオになります。
設定パネルは隠せるようにして、隠すと1弦の上にもう1本、開放がB4の「0弦」が出てきます。これは7弦ギターを高い方に伸ばした発想で、ソロの高音域が楽になります。
Let It Beのアルバム版ギターソロを通しで弾けるように、0弦はB4を開放弦にしました。A4だと最高音にちょっと足りない。
本体を振る
Tab5にはBMI270という6軸センサーが入っています。iPad版にもある、本体を揺らすとビブラートがかかる機能を移植しました。
これは、iPhoneアプリのPocket Guitarが最初から持っている機能です。これがあるおかげで、非常に表現力の高いビブラートが手軽に出せます。
加速度から重力成分を取り除き、揺らしている方向を自動で追いかける軸に投影して、その値をそのまま音程に加えます。揺れの速さがそのままビブラートの速さになります。画面を叩いた衝撃を拾わないように、80ms以上揺れが続いたときだけ効くようにしてあります。
さらに、本体を右に鋭く突き出すとフレット範囲が上がり、左に突き出すと下がるようにしました。最初はタッチのフリックで実装したのですが、弾いている最中のフリックは速いスライドと区別がつきにくいので、センサーに変えました。
これで、ギタリストがネック上で手の位置を変えるようにポジション変更が可能になります。
困ったのは、センサーの取り付け向きの資料がないことです。Claude Codeは、本体を傾けて持ったときの重力の向きから画面の上下を推定し、その直交方向を左右とみなす方法で逃げました。筆者が左右を確認したら、たまたま一発で合っていました。
スケールに対応
キーがAならブルーノートはどこか、スケールが見えた方がいい。
ルートとスケール種別(メジャーペンタトニック、マイナーペンタトニック、ブルース、メジャー、マイナー、ドリアン、ミクソリディアン、ハーモニックマイナー)を選ぶと、スケール上の音が青緑、ルートが琥珀色に塗られ、各マスに音名が出ます。Aのブルーノートスケールにしておけば、色が変わっているところを押さえていればミストーンを出しません。
さらにSNAPモードを入れると、外れたマスを押しても一番近いスケール音に寄せて鳴ります。チョーキングやビブラート、スライドは補正せず、鳴っている音から連続的に動きます。
フレットを見ていなくても、一応はスケールに沿った、表現力豊かなフレーズを弾けるはずです。
無限音楽機関も入れる
ギターが弾けるようになると、欲が出ます。筆者はブラウザ向けに、自動演奏アプリの無限音楽シリーズを
作っていました。キース・エマーソン風のMoogソロを延々と生成する「Emerson Moog Infinity」、バッハの様式で無限に続く「Bach Perpetuum Mobile」、サティのジムノペディをほどきながら新しい曲を紡ぐ「Satie Infinity」です。これら3つをTab5に入れて、APPSメニューで切り替えられないか。
どれもJavaScriptで書かれていて、音はTone.jsやWeb Audioのシンセとサンプラー、作曲はコードで書いたルールです。Claude Codeは3つのリポジトリを読み、まず共通のDSP部品をC++で用意しました。PolyBLEPのオシレーター、Moogのラダーフィルター、biquad、ADSR、ディレイ、リバーブ、歪み、コンプレッサー。その上に、各アプリの作曲ロジックと音色をそのまま移しています。
Emersonは16種類のフレーズ生成器、5種類のMoogと2種類のHammondの音色、ベース、Palmer風のドラム、それにテンションが上がると曲が激しさを増していく「FURY」の周期まで載りました。画面にはノブの動きとHammondのドローバー、ロータリースピーカーの回転、鍵盤が出ます。CPU負荷はシングルコアの4割ほどです。
Bachは7つの様式を持っています。トッカータとフーガ、コラール前奏曲、パッサカリア、アリア、プレリュード、インヴェンション、そしてリュートのブーレ。ヴェルクマイスターIIIの音律と、a′=415Hzのバロック・ピッチ。チェンバロとリュートはKarplus-Strong、オルガンは倍音加算で、画面には楽典の実況が日本語で流れます。

Satieはピアノのサンプラーです。ブラウザ版が使っているSalamanderピアノのサンプルを30音、22kHzモノラル、2.6秒ずつに切り詰めて3.4MBにし、ファームウェアに埋め込みました。16MBのフラッシュのうち、アプリ領域の83%が埋まっています。起動時にPSRAMへ展開し、五線譜の上を音符が流れ、鍵盤に落ちていきます。
音が出ない、音が汚い
動いたのは動いたのですが、ここからが長かった。
まず、Emersonを鳴らして数分すると音が出なくなり、FretCasterに戻しても出ません。データは流れているのに音がない。1Wのアンプを数分間フルスケール近くで鳴らし続けたので、アンプの保護が働いたのだろうという見立てで、出力の長期平均を抑える「ガバナー」を入れました。再起動すると音は戻りました。
次に「プチプチ」というノイズです。これは画面描画の影響でした。Tab5のパネルは実は縦向き(720×1280)で、横向きの表示はM5GFXが1ピクセルずつ回転しながら書いています。Satieの五線譜(1280×422)の転送に66~160msかかり、その間PSRAMの帯域を使い切って、別のコアで動いている音声計算が間に合わなくなっていました。
Claude Codeは、ESP32-P4に積まれている2Dアクセラレーター(PPA)を見つけてきました。回転と拡大縮小をハードウェアでやるブロックで、五線譜をPPAに渡して縦向きのフレームバッファへ直接回転転送させたところ、27msになりました。色が入れ替わるバイトオーダーの問題を、Claude Codeがスクリーンショットをシリアル経由で吸い上げて自分で確認しながら直したのは、見ていて面白いところでした。
「バッハが半音低い」と指摘したのですが、これはバロック・ピッチの415Hzが初期値だったからで、ボタンで440Hzに切り替わります。元のブラウザ版もそうなっているのですが、スピーカーから聞くと確かに違和感があります。
「エマーソンの音像がぼやけて、楽器が分離しない」。別のスピーカーでも同じだったので、信号の問題です。原因はリバーブでした。ブラウザ版のTone.Reverbは残響のインパルス応答をエネルギーで正規化しますが、移植したFreeverb型はしていません。7.5秒の残響を持続音のオルガンに通すと、残響がドライを大きく超えて、全部が洗われていたのです。
正規化を入れ、200Hz以下を残響に入れないようにしたら「音が良くなったね」と言えるところまで来ました。そのあと耳で2段階下げて、残響は4秒、ウェットは元の4分の1に落ち着いています。
Satieの残響も入れ替えました。「リバーブはここまで強くなくてもいい」という注文に合わせて量を減らし、アルゴリズムもFreeverbからDattorroのプレートリバーブに替えています。ピアノの残響はこちらの方が自然で、遅延線を内蔵RAMに収めたので、PSRAMの帯域を音声計算と取り合うこともありません。

ノイズを計測で追い詰める
最後まで残ったのがSatieのノイズです。「最初はクリアだったのに数分後から乗り始めた」「音量と比例して増える」と指摘。
Claude Codeはここで、耳ではなく計測に切り替えました。本体の中で計算し終えた音声を4秒間PSRAMに溜めてUSBで吸い出し、クリックを検出する。10分間、2分おきに解析してもクリックはゼロ。本体の温度センサーは40℃で安定。ならばと純音だけを鳴らすテストアプリをAPPSに足して筆者に聞いてもらうと「純音は綺麗だった」。スピーカーもアンプも無実です。
決め手は、計算したブロックの累積数でした。48kHzで256サンプルずつなら毎秒187.5ブロックのはずが、166~183しか出ていない。つまり数%の音が抜けていて、それがプチプチの正体でした。「数分後から」は曲が密になって同時発音が増えたから、「音量と比例」も同じ理由です。
原因は、ピアノの再生位置をdoubleで進めていたことでした。ESP32-P4のFPUは単精度なので、doubleの計算はソフトウェアで処理され、1サンプルごとに数百サイクルを食います。FretCasterの移植で最初に直したのと同じ落とし穴に、別の場所でもう一度はまっていたわけです。整数と単精度の組み合わせに書き換えたら、計算時間は1ブロック5.3ms枠のうち平均2msに落ち、90秒間の計測で欠落はゼロになりました。
実際に聴いてみても、ノイズはなくなりました。

次はどのデバイスに移植する?
一通り動いたところで、「さらに移植するならどのデバイスがいいか」をClaude Codeに聞いてみました。
本命は、同じESP32-P4を積んだWaveshareの10.1インチ版(ESP32-P4-WIFI6-Touch-LCD-10.1)でした。800×1280のタッチパネルにコーデックとスピーカー、microSD、電池のオプションがついて80~90ドル。チップが同じなら、PPAの回転転送も単精度前提のDSPもPSRAMの配置もそのまま動きます。M5Unifiedがこのボードを知らないので、ディスプレイとコーデックの初期化は書き直しになりますが、音源と作曲とUIのロジックは無改変で済む見込みです。
ギターにとって効くのは面積です。5インチに10フレットを並べると1フレットが約1.3cmですが、10.1インチなら約2.5cmで、実物の12フレット付近に近づきます。5インチでは諦めた12フレット表示も戻せます。
別の路線はRaspberry Pi 5にタッチパネルをつける手で、自動演奏の3つはブラウザ版がそのまま動きます。ただし起動に数十秒かかり、電源はモバイルバッテリー頼み。「電源を入れたら楽器」というTab5の手触りは薄れます。Teensyのような音声特化のボードは音は一級ですが、大きなタッチ画面がないので、今回のような画面主体の構成には合いません。
一つ心配なのは、フレームからディスプレイまでの距離がけっこう大きいこと。それだと、FretCasterでは指がフレットの5、6限に届かない可能性があり、操作性も悪い。もっといいデバイスがないか、調べてみようと思います。
結局、サイコロ型のM5Stack「AtomS3R」という製品をオーダーしました。これに無限音楽機関を入れて、サイコロを転がして、みたいなことができるといいかな、と。
¥4,070
(価格・在庫状況は記事公開時点のものです)
現状
ここまでが、長崎に着いてから3日ほどの成果です。APPSメニューには、FretCaster、Emerson、Bach、Satie、それに純音テストが並んでいます。ギターを弾いて、飽きたらバッハを流しておく、という使い方がTab5一台でできるようになりました。
Tab5で覚えたことは4つあります。ESP32-P4のFPUは単精度で、doubleを1サンプルごとに使うと音が抜けること。パネルは縦向きで、横向きの大きな転送はPPAに任せること。1Wのアンプは鳴らし続けると落ちるので、出力の長期平均を抑えること。そして、耳で追えないノイズは本体の中で計測すること。どれも次の器に持ち越せます。
ギターの音色はまだiPad版に追いついていませんが、ヘッドフォンジャックからミキサーに挿せば、Tab5だけでステージに持ち込めるサイズと重さではあります。
無限音楽機関シリーズをデモするのにもちょうどいい大きさです。
次はコードストリップの曲ごとの編集と、USB経由のMIDI出力あたりでしょうか。Tab5の開発はいったんここまでにして、同窓会で演奏する、ビートルズやジュリーの曲をちゃんと練習しておこうと思います。












