フェルマータ (original) (raw)
この広告は、90日以上更新していないブログに表示しています。
概要
少しお休みを取ることができたのでそれを利用して開発をしようと思ったところ、 Claude Code が web 画面上からも使えるそうで $250 分のクレジットを無料でくれるとのことで使ってみた。
使い方は Devin と変わらずで GitHub に接続して自分のアクセスできるレポジトリに対してクラウド上で Claude Code が動いてくれるものであった。ちょうどよく単純な VST3 エフェクトが作りたいなと思っていたのでこの実装を Claude Code に任せることとした。
VST3 エフェクトって何?
音楽作成系のツール上で使える音響エフェクトのフォーマットで、例えばカラオケマイクのエコーみたいなのを作る規格。Steinberg 社の作った規格だが DAW 周りについてはそれなりにデファクトの規格になっている。詳細は下記の通り。
なんのこっちゃ、という感じだが .a ファイルや .DLL ファイルを規格に合わせて作ると、音に対するエフェクトが作れるよというものになっている。 C++ での実装になる。
要件
- LR 両方の音源の Pan 位置と音量をL100-R100の間で調節できる
- 例えば L の音を R40, -3dB、 R の音を L 100, -6dB などが可能
- LR 両方の音源を 0~100ms の間で遅延させられる
- これはハース効果狙い
- 1 のパン位置 はスライダー、1 の音量変更と 2 はノブで調整できる
ただこれだけのものでステレオ音源の左右の位置を変更でき、かつ左右の音それぞれに遅延がかけられるものが欲しかった。定位と言って音の位置を設定するのにこれがあると便利だったりしたのだが昔使っていたフリーのものがリンク切れしてしまったので作りたかったのである。
できあがったもの

制作物のスクショ
要件から見て取れる通り音響処理はそんなに難しくないので、音響側の処理は一瞬で実装できたがどちらかというと UI のがきつかった。動かしてみたい人は下記 GitHub からコンパイルしてほしい。 Cursor の CMake: build のコマンドを cmd から叩いたら出来上がった。 Windows で環境構築ほぼなしで行けるのはちょっと感動。
GitHub - haruneko/SimplePanner: Simple VST3 Panner
AI による指示の過程
今回は Spec-Driven Development っぽくやろうと考えていた。cc-sdd などが流行りなのは知っており、仕事の合間に触っていたのだが、これは結局指示出しの問題で、大きいものであれば指示出しを共通化すればよいがどうせ大した量がないのでフリーハンドで同様のプロンプトを書くことにした。
要件定義まで

最初の指示
SDD する前にプロジェクトの初期化だがこれも AI に指示しておまかせ。意外に CMakeList の書き方がうまく、自分でやるより良かったと思っている。

要件定義
SimplePanner という名前と VST3 をほのめかしたことでビルドまで完了してくれたので要件をお伝え、これはもう人間に伝えるのと同様だが簡素な指示とした。

要件定義打ち返し
あとは requirements.md が出てくるのでこれを承認して、詳細設計の design.md を起こしてもらい、タスクに分解してもらった tasks.md。実装についてはもう上から順に実装してもらうだけなので割愛。途中で GitHub workflow を作ってもらったりはしたが基本 tasks.md を実装してねとお願いし続けるだけ。
今回の困りポインツ
リンカのエラー読み慣れていなさそう
C++ はコンパイルエラーもエグいがリンカのエラーもエグい。 C++ 特有の文脈ではあるものの下記のように純粋仮想関数の実装が見つからない、とリンカが怒っているのがわからず数回リンカエラーは CMakeLists.txt 側の問題だろうと違うところをいじっていた。エラー文言読めばわかるとは思うのだが難しかったようだ。下記のようにずっと言い続けて直せなかった。

リンクエラーが解決できない AI ちゃん
結局純粋仮想関数の参照がないエラーにしか見えなかったのでちゃんとエラー見るようにお願いした。大興奮で直してくれた。 AI ってときどき興奮して視野が狭くなりますよね、感動モードと固執モードみたいのがある印象。

真因にたどり着いたときの AI ちゃん
終わりに
これ大体 1 日で $45 分のクレジットなのでこのやり方でこのレベルのものが出るのは非常にやすいと思う。一方でこれ以上大きいものになってくると sdd の各種ツールを使ってやり方を固定するほうが良さそう。特にテスト実装は指定しないとサボり始めるのでプロンプトに常に入るようにしたほうが良い。あと Claude Code の web 版については統合が微妙な感じではあるが、仕事で使っていた Devin より快適であった。環境構築とか自分でやってくれるし。また、ローカルと違っていい環境にいるのか API アクセスで詰まる、みたいな感じがなく若干早く実装が終わる印象があった。いずれにせよ自分の不得意なところはもう AI に任せて検収をこっちでやる形になっていくんだなと思った。
概要
上記の記事のように Jules という自律型コーディング AI エージェントが発表され、 2025/06/01 現在でベータテスト中ということで使ってみた。ご存知の方も多いと思うが Github Copilot や Cursor のように伴走するようなエージェントではなく、 Devin 同様に指示出ししておけば AI が勝手にコードを書いたり修正したりしてくれるものとなっている。 GitHub と接続して作業レポジトリとブランチを指定し下記のように指示を出す。

Jules への指示出し画面
すると Jules が実装計画を投げてくるので Approve すれば、あとは待っているだけで PR が上がってくる。ちょうど Gemini DeepResearch と同様の UX である。今もこの記事の作成中裏で Jules はコードを書いている。
下記から試せるので使ってみたい方はここからアクセスしてみてください。
βテストでできること
記事作成時の 2025/06/01 日本時間では下記ができる。
- 5 並列でのタスク実行
- 日に 60 回までのタスク実行制限
- 日に 5 回のコードキャスト
60 回もタスク実行できてしまうのでこの土日で遊ぶ分には非常に楽しめた。ただ制限はテスト中に変わるでしょうからあくまで現時点のものである。
書いている人のバックグラウンド
やれることのレベル感をお伝えするのにバックグラウンドがないと厳しそうなので記載しておく。
- キャリア感
- Web エンジニア 13 年目
- バックエンド・インフラ・マネジメント寄り
- 最近はプロダクションコードはまあ書いていない
- AI エージェントの導入具合
- 4 月くらいから Cursor 使っている
- Devin 使いたいけどほんま小粒が多くて使えてない
- プライベート
- 2 歳, 4 歳の子どもあり共働きフルタイム家事育児半々
なお今回採用した tauri + React + TypeScript は TypeScript は普段使っているものの私には他の2つは知見がまったくない。
何をしたのか
VOCALOID や UTAU といったピアノロールを利用した歌声合成の UI の入りを作ろうとしてみた。環境は Windows ネイティブ。最初のプロンプトに書いたとおりだが下記のような感じ。
プロンプト
tauri のネイティブアプリ向けプロジェクトを作ってください。 React+TypeScriptのフロントにしてほしいです。
tauri の UI 部分にボーカロイドや UTAU, Synthesizer V などのようなメロディを扱うピアノロールを作ってください。 ピアノロールの下にはコントロールカーブなどの編集スペースを作るためあとから入れられるようにするか、ピアノロールを独立したコンポーネントとして作るかしてください。
これをプロンプト変えながらわーわー言って作ってもらった。なお私は今回コードを一行も実装していない。
何ができたのか&感想
2日間、子どもの世話と買い物と家事をしながら合間を見て指示出しをする形で、自分は一切コードを書かずに Jules だけにコードを書かせてみた。できたものとしては下記のような感じ、自分で集中する時間取って書くより数倍では効かないくらい早い。というかそも二児の世話と家事を半分していたら集中タイムなんてほぼゼロでなにも作れないんだからゼロの何倍かは測れない。これで家事と育児はちょっとしかサボっていない(と思っているものの配偶者に言わせれば噴飯ものかもしれないが)ので驚異的だ。

成果物
案外マネジメントやりながらのコード修正に近い感じがして、慣れたコードベース上であればおそらく自分より実装の早い自分の分身(ただし指示は多めに必要なエージェント)に実装を任せられる。これはマネージャー等実装に時間の割けない人間にとってはかなりの革命のように思う。とはいえ指示出しとコンパイルは実施しているわけでそのための時間はどうしても必要。実装面側は久しくメインで実装業務をしていないので、そちらの視点の感想は他の方に譲るとする。
また、生成物は下記のレポジトリに出力しているので興味がある方は参照ください。
TIPS と気になった点
プロジェクトの初期構築は毎回構成が異なるので自分で誘導したほうがいい
何回か tauri のプロジェクトを作ってもらったが、そもそも外部からのファイル作成数上限に引っかかりうまくいかないときもあるし、毎回構成が違う。パッケージマネージャが pnpm なのか npm なのか yarn なのか。指示出ししていない私が悪いと言われればそれはそう。でも知識ゼロだったんだもん、 Gemini にでも聞いたら良かったか。
ビルドやテストを実行してほしいときはきちんと指示すること
最初何回か自信満々に Jules が出してくるコードを手元でビルドするとビルドが通らない。子どもが騒いでるから全部消して指示出しし直そう、みたいなことが2~3回あった。有料版になると値段が怖いので指示出しちゃんとしましょう。

指示出しの具体例
これは X で下記のように AK Kulkarni 氏(Jules の PM の方とのこと)から教わりましたがこうしておくだけでちゃんとビルドとテスト通してくれるようになるのですごい。氏に感謝。
That’s great! Jules has the ability to actually run the code - try to ask it to compile the code after it’s done and verify its changes. You might have to be a bit forceful and give it some help (we’re working on making this easier)
— AK Kulkarni (@itsakdev) 2025年5月31日
指示出しは具体的に
当たり前体操。これは Cursor でも同じなので設計が頭にあるならちゃんと言葉にしておく必要はある。私は上述の粒度で指示してうまく行ったように感じる。ただし React のところは正直私のレベルが低すぎて私と同等よりちょっと良いくらいにしかなってないと思う。
コンテキスト保持強めっぽいので一貫するタスクは一つのタスクでやる
Gemini は DeepResearch と素の 2.5 Pro をよく使っているのだが、コンテキストを長期間保持できた上に薄まらないし判断を間違えない印象がある。今回面白かった例を挙げておく。
PianoRoll の実装タスクで下記のように指示を出した。
プロンプト
tauri でフロントエンドが React, TypeScript のアプリ上に UI 側のデータ構造である、 Note, Event, Control を追加しました。
このアプリでは VOCALOID や UTAU, Synthesizer V と言った歌声合成に関わるエディタを作ろうとしています。今回はフロントエンド側のデータ構造のうち、特に Note について UI を作っていきます。3つのコンポーネントを作ります。
* PianoKeyboard コンポーネント
* PianoRoll の左に表示されるピアノ鍵盤を表す
* 白鍵・黒鍵によって色分けされており鍵盤ごとにノート名、 C6 などが書かれている
* C6, note=60 のところは基準ドなのでハイライトされたい
* 0-127 の 127 ノートを表示し、それ単体では表示域の調整を持たなくてよいです
* PianoRoll コンポーネント
* ピアノロール本体で 0-127 まで表示されそれ単体では表示域の調整を持たなくてよいです
* 罫線についてはアーキテクチャ上非常に要素が増えてしまうので div 等ではなく canvas で実装してください
* PianoRoll 上にノートが配置されるようにしてください
* Note コンポーネント
* ノートの表示と編集を行うコンポーネントです
* クリックで選択できます
* 選択時に Del キーで削除できます
* 選択時に Enter キーでテキストボックスによる歌詞の修正ができます、再度 Enter で確定し Esc でキャンセルします
* 選択時に Tab で次のノートが選択されます
* ドラッグ&ドロップで位置と音高を変更でき音高はグリッドにひっつく形にします
* 右端をドラッグ&ドロップで長さを調整できます。
作成後テストが必要ならテストも追加してください。
README.md を変更に合わせ更新してください。必要とあらばファイルを分割し、 README.md からリンクをはるかたちにしてもよいです。
最後にビルドやテストなど README.md 上の手順をひと通り試しコマンドが正しく実行できるようにしてください。
この指示からできた UI がこれ。

左に PianoKeyboard 右に PianoRoll
なんやねんと思ったが指示が悪い。普通はボカロエディタなんて使ったことないもんね、と思い直しそれなりにしっかり作られているので再指示を実施。
プロンプト
実装ありがとうございます。指示出しがよくなかったので PianoKeyboard と PianoRoll の関係について、 PianoKeyboard は今回水平方向にキーが広がっていますがこれは誤りで、 PianoRoll の左側に PianoKeyboard を置くことで PianoRoll の各罫線の高さを表すコンポーネントとなる想定です。 そのため PianoKeyboard の向きを変えたうえで PianoKeyboard と PianoRoll はくっつけて表示したいです。 また、追加で依頼ですが PianoRoll は黒鍵の部分に薄い色を追加してほしいです。 指示出しで不明点あれば私に聞いて下さい。
Jules
フィードバックありがとうございます!
1. PianoKeyboard の向きは、垂直方向に変更し、各鍵が PianoRoll のノートの高さ(ピッチ)に対応する形でよろしいでしょうか?(例:上が高音、下が低音)
2. PianoKeyboard と PianoRoll を「くっつけて表示」とは、隙間なく配置し、各鍵の高さとピッチレーンの高さを一致させるという理解でよろしいでしょうか?
3. PianoRoll の黒鍵部分の薄い色は、黒鍵に相当するピッチの行(レーン)に薄い背景色を付けるという理解でよろしいでしょうか?
詳細を教えていただけると幸いです。
再質問あり、急に日本語喋るじゃん助かる。
プロンプト
1. はい、正しいです。 PianoRoll と合わせる都合上鍵盤一つ一つの高さも PianoRoll と合わせたいです。
2. 良いです。ただし PianoKeyboard の幅を調整できるよう、 PianoKeyboard と PianoRoll の間の境界線をドラッグアンドドロップでサイズ変更する可能性はあります。
3. あってます!色については一旦お任せします。
再回答して出てきたのがこれ。

いい感じに少しなった
それなりに良さそうなので再指示。
プロンプト
ありがとう。とても良い感じになりました。 7点修正されたいです。
PianoRoll を表示しているエリアの Piano Roll Area のラベルは不要です。
PianoKeyboard を表示しているエリアの Piano Keyboard Area のラベルは不要です
PianoRoll を表示しているコンポーネントにパディングなどの設定があるので0にしてほしいです
PianoKeyboard を表示しているコンポーネントにパディングなどの設定があるので0にしてほしいです
PianoKeyboard の白鍵の高さをしっかり見るために境界線を 1px 追加してください
PianoKeyboard の黒鍵の幅は白鍵と同じで良いです。ビジュアルよりこちらを優先してください
PianoKeyboard と PianoRoll のスクロールを同期させてください
Jules
フィードバックありがとうございます!7点の修正点、承知いたしました。
1点確認させてください。「PianoKeyboard と PianoRoll のスクロールを同期させてください」についてです。 現状、これらのコンポーネントは全域(0-127鍵)を表示しており、単体ではスクロール機能を持っていません。App.tsxのメインコンテンツエリアにスクロールが発生する場合、その親のスクロールを同期させるイメージでしょうか? それとも、各コンポーネントに表示範囲を限定する機能(例:画面に収まらない場合にスクロールバーを出す)を追加し、そのスクロールを同期させるイメージでしょうか?
この点をもう少し詳しく教えていただけますでしょうか。
プロンプト
いまって、 PianoKeyboard も PianoRoll も同じコンポーネントに入ってるんでしたっけ?同じなら親のスクロールを同期させるで良いと思いますが、私はなにか変なこと言っていますか?
Jules
ご説明ありがとうございます!
現状、App.tsx 内の main-content という div が PianoKeyboard と PianoRoll を含んでおり、この main-content が画面の高さを超える場合にスクロールします。
この main-content のスクロールを同期させる、つまり main-content がスクロールされたら、PianoKeyboard と PianoRoll の表示されている部分が常に同じ高さになるように調整する、という理解でよろしいでしょうか?
この理解でよろしければ、新しい計画を立てさせていただきます。
プロンプト
良いです!!よろしくお願いいたします!!
これで出てきたのが成果物なので同一タスク内での一貫性がかなり良いように思う。 Gemini のコンテキスト窓が長いのは感じていたけれどちょっとすごいと思った。
とりあえず記事にしておくべきだと思ったのでこんなもん、今日はここまで。
概要
A.I. Voice の琴葉姉妹を購入したので、 Text2Speech で作成した音声をあとからちょっと色付けできるツールを作ってみた。ただ、私としてそこまで Text2Speech をやりたかったわけではないので、これが使えるツールになっているのかはわからない。どちらかというと下記の記事の続きものであり技術検証じみた趣味の開発であって、自身のニーズすらないところで作っているので的外れ感はあるかもしれない。
成果物とやれること

スクショ
*
44.1kHz 16bit の音声ファイルを下記のように編集できる
- 波形の上でダブルクリックすることで区間を分ける
- 区間の長さをドラッグアンドドロップで伸縮ができる
- 下の部分の制御点(四角いやつ)をドラッグアンドドロップで音声の高さ(F0: 周波数, GEN: 声質の高い低い)を調整できる
- 下の部分でダブルクリックすることで制御点を追加できる
また、有声・無声区間を分けることで簡単な区間わけができるようになっている。 undo, redo とかでは実装する気力がなくてやれていない。
技術的所感
はじまりは自身の C++ のライブラリを TypeScript から使いたかったので emscripten を使い始めた。たしかに emscripten は C++ のごとき操作性を TypeScript の世界に持ち込んでくれる。が、いささか過保護であり C++ の世界観を持ち込むために wasm の限界を忘れているように思う。というのも wasm は VM とその VM 上で使える関数とメモリ領域を持っているだけなのに、あたかも C++ のように見せたいがため wasm 以外の JavaScript の領域にあまりにはみ出しすぎている。その流儀も C 言語の流儀に近く、 wasm が売れたとしてでは emscripten ごと受け入れられるのか?と言われるとやや懐疑的に見える。結局 wasm の限界をちゃんと見定めないと今はいいけどあとで死にそうだなと思った。あくまで wasm はプリミティブで処理速度の必要な範囲でとても小さく作るべきだなと思った。 一方で react はとても使いやすかった。理解しやすいし作りやすい。ただ、状態管理を行う際にシャローコピーやディープコピーといったプリミティブなところを結局気にしなければいけなかったり、 react 自身の暗黙の副作用を理解する必要があってつらいなあと思った。まあでもこれは過去 Qt を使って SIGNAL/SLOT を使っていたときよりはだいぶ楽になったと思う。 総じて見れば家事・育児を半分引き受けつつ仕事をしながら三ヶ月でこれくらいのものが作れるのであれば、案外余裕はあるんだなという所感を得た。一方で家族と話したり顔を見たりする時間は確実に減るので両立できないもどかしさは残る。トレードオフなので仕方ないか。
概要
C++ の vocoder に GUI を作ろうと思ったが、 C++ with Qt とかだとあんまり一般的な知見を得られないな、じゃあ React を使おう。ということで軽く言ったもののあんまり中身は軽くなくて下記のように苦しんでいた。が、ようやく React の世界に持ってこられたのでサンプルを作ってみた。これが私の React 入門編です。正直に言うと React + emscripten は全然入門じゃないと思う。
作ったもの

https://haruneko.github.io/voice-editor-practical-impl/
やっていること
この C++ で書かれたライブラリを
emscripten を利用してここで npm package 化し
ここに公開されたパッケージを読み込んで
いい感じに読み込んで使ってみている。
感想
ツールとしては微妙だけれどやりたかった C++ のコードを利用する GUI を React というか TypeScript で書くのはできた。思ったよりも WebAssembly が軽快ではないため、ちゃんと作るなら並列処理をどうにかやらないと厳しいかもしれない。また、 F0 を編集できるようにすれば今よりは使い出のあるツールになると思うのでその辺まで作ってみたい。並列化よりはそっちのが楽でしょうし。 しかし今回 emscripten/wasm も React も初めて触ったけどこれは断じて入門編ではないですね、作りたいものがこれだったから仕方ないしチュートリアルやったときは React は髪の毛ほども理解できなかったのが今回は理解できたしいいけれど…理解のためには React と emscripten 両方とも本を通しで読みましたので末尾に書いておきます。
網羅的に書かれている。ただし emscripten の glue コードを web app から使うためのコードはなかった。なかったんです。
3まで全部読んだ。網羅的にやってくれているのでこれを読んだ後に web の記事漁りながらコード書くのが効率良さそう。ただ、 TypeScript 触ったことなかったらこの本読むだけでも詰んでたなという気持ちがある。
概要
すでにタイトルがなろう小説並に重たい。下記 2 エントリの続き物で今回ようやっと納得行くところまで作れたのでブログ記事にしておく。
- CMake の C++ プロジェクトを nodejs, TypeScript の世界に持っていく - フェルマータ
- Emscripten を使って C++ のコードを JavaScript の世界に持っていく - フェルマータ
やることはタイトルに書いてある通りなのだけれど詰みポイントが何箇所かあったのと、結構いろいろ知らないといけないことが多くて全部を書くととてもまとまらないので、結論を先に書いてから個人的にすごくつらかった wasm を webpack に読ませる箇所の回避方法を紹介する。
成果物
とりあえず今回の成果物。タイトル通りのことをやっているが、いかんせん C++ にせよ nodejs にせよ本職の範囲から離れているのでこれがいいやり方かは知らない。動いたからいいやくらいの精神の成果なので参考にする際はご注意を。
詰みポインツと回避方法
実は過去記事で nodejs から使うのはあっさりできていて、簡単だったのだけれど wasm と emscripten の glue コードを webpack (react-app)から読むのが大変だった。大した情報もネット上になかったのでこの問題を解決するのにだいぶ時間がかかった。問題は大まかに2つ。
- emscripten の作成する glue コード(js ファイル)が wasm ファイルに依存しているので webpack で wasm を配信できるようにする必要がある
- emscripten の作成する glue コードそのままだと webpack で配信する wasm ファイルのパスを渡せない
だいたい glue コードのせい。
wasm を webpack で配信する
react-app を利用していたので webpack の設定をいじるために cracoを利用した。 wasm ファイルをみたら javascript/auto で配信してね、と emscripten の glue コードに nodejs 用の path, fs を利用するコードが含まれているので無視するように設定している。
module.exports = { webpack: { configure:{
module:{
rules: [{
test: /\.wasm$/,
type: 'javascript/auto',
}]
},
resolve:{
fallback: { "path": false, "fs": false }
}
}
}};
wasm が配信できないのはこれで解決。ただしこれだけでもダメで、 webpack が配信する際に wasm ファイルの名前を変えてしまうので emscripten の glue コードにファイル名を渡す必要がある。
glue コードに配信している wasm のファイル名を渡す
なかなか発狂ものなのだが react-app から emscripten の glue コードを直接使っている場合単純にこれができない。なので emscripten のコンパイル時に pre.js, post.js によしなな JavaScript を書いてファイル名を渡せるようにする。
- pre.js
var uzumeInitJsPromise = undefined;
var factory = function (moduleConfig) {
if (uzumeInitJsPromise){
return uzumeInitJsPromise;
}
uzumeInitJsPromise = new Promise(function (resolveModule, reject) {
var Module = typeof moduleConfig !== 'undefined' ? moduleConfig : {};
var originalOnAbortFunction = Module['onAbort'];
Module['onAbort'] = function (errorThatCausedAbort) {
reject(new Error(errorThatCausedAbort));
if (originalOnAbortFunction){
originalOnAbortFunction(errorThatCausedAbort);
}
};
Module['postRun'] = Module['postRun'] || [];
Module['postRun'].push(function () {
resolveModule(Module);
});
module = undefined;post.js
return Module;});
return uzumeInitJsPromise;
}
if (typeof exports === 'object' && typeof module === 'object'){ module.exports = factory; module.exports.default = factory; } else if (typeof define === 'function' && define['amd']) { define([], function() { return factory; }); } else if (typeof exports === 'object'){ exports["Module"] = factory; }
もともと emscripten には本当は Module.locateFile を上書きして wasm のファイル名を解決する方法があるのだがブラウザビルドで利用できないので無理やりこじあけておく。その上で下記のように wasm のファイル名を渡せばよい。
import uzumejsWasm from "!!file-loader?name=uzumewasm-[contenthash].wasm!uzumejs/resources/uzumewasm.wasm";
...(中略)...
const u = await uzume({ locateFile: () => uzumejsWasm });
終わりに
先人の挑戦がなければ無理でした。 nodejs, webpack 初心者にわからんよこんなの…下記の sql.js のコードを見てなんとか理解した感じがある。ここまでやる必要があるし、その上でパッケージ利用者に負担がかかるのか、と悲しい気持ちになりました。とりあえず Rust でやればいいんじゃね?という気持ちになりました。まあ C++, C のコードは TypeScript の世界で動かしたい放題にはなったのでそれはよかったということにしておきます。
ポエム
プライベートの開発環境は Qt with C++ を cmake でプロジェクト管理というスタイルだったのだが、いかんせんウェブエンジニアである本職とあまりに乖離していたので、もう少し本職に寄せておきたかった。最近は WebAssembly なるものもあり聞けば C++ のコードを WebAssembly にコンパイルして JavaScript から扱うことができるという。なので自作の C++ ライブラリを JavaScript で利用できる形にして、さらに nodejs プロジェクトで TypeScript から呼び出せるようにすれば、 GUI は web の世界に持ってこられるだろうと思い、今回は C++ を WebAssembly にして nodejs, TypeScript の世界に持ってくることにした。
やったこと
いままで Windows 環境で Windows なりの環境構築が厳しくなってきたので、 WSL2 をインストールして WSL から Ubuntu 使って開発することにしたので特筆なければ下記でやった内容は WSL, Ubuntu でやったことと思ってください。 10 年前後昔の環境でやってたらさすがになあ。
emscripten
cmake プロジェクトを WebAssembly (以下 wasm)にコンパイルするのに必要。細かいコマンドまで見ると面倒だが試しに使う分にはインストール後 cmake のオプションからパスを指定するくらいなのであまり意識しなくて良かった。
インストールに関しては難しいことはない。公式のコマンドコピペでおわり。
CLion
こちらの方のセットアップをそのまま利用した。 CLion だけはまだ WSL での開発になっていないのでここは Windows で実施した。 emscripten に JavaScript にしたい C/C++ の機能に目印をつける役割のコードは C/C++ で書くので設定が必要。
C++
emscripten の機能である embind を利用して JavaScript にどの機能を使えるようにしていくか書いていく。例えば時系列のデータを格納する Contour クラスだと下記のような感じ。
// Copyright 2022 Hal@shurabaP. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. #include "data/Contour.hpp" #include <emscripten/bind.h>
using namespace emscripten;
EMSCRIPTEN_BINDINGS(ContourBindings) { class_uzume::vocoder::Contour("Contour") .smart_ptr<std::shared_ptruzume::vocoder::Contour>("shared_ptruzume::vocoder::Contour") .constructor(&std::make_shared<uzume::vocoder::Contour, double, double>) .function("at", &uzume::vocoder::Contour::at) .function("msLength", &uzume::vocoder::Contour::msLength) .property("length", &uzume::vocoder::Contour::length); }
これは割と公式ドキュメント通りなので困らない。インタフェース的にメソッドがいくつか足りない気がするが本筋でないので今回は対処していない。
CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(uzumejs)
include(ExternalProject)
file(GLOB_RECURSE VOCODER_SOURCES ./lib/uzume_vocoder/src/.cpp) file(GLOB_RECURSE VOCODER_HEADERS ./lib/uzume_vocoder/src/.hpp)
include_directories(uzumejs PRIVATE ./lib/uzume_vocoder/src/)
add_executable(uzumejs ... # 割愛 )
if (EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".js") set(lflags "--pre-js CMAKESOURCEDIR/src/resources/pre.js−−post−js{CMAKE_SOURCE_DIR}/src/resources/pre.js --post-js CMAKESOURCEDIR/src/resources/pre.js−−post−js{CMAKE_SOURCE_DIR}/src/resources/post.js -s WASM=1 -s MODULARIZE -s 'EXPORT_NAME="factory"' --no-entry --bind") set_target_properties(uzumejs PROPERTIES LINK_FLAGS ${lflags}) endif()
ここは結構厄介だった。ポイントを追って書いていく。
- set(CMAKE_EXECUTABLE_SUFFIX ".js")
emscripten を通したコンパイルは実行ファイルを作ってくれるわけだが、ネイティブと違って JavaScript の世界では実行ファイルはこれ、という明確なのは指定しづらい。のでコンパイル結果の拡張子でどのファイルを出力させるのかを emscripten にお伝えする必要がある。
| suffix | 出力内容 |
|---|---|
| html | HTML + JavaScript + wasm |
| js | JavaScript + wasm |
さらに行くと wasm だけ出力するオプションもあるのだが今回は割愛する。 .html が指定されたときには出力された HTML が そのまま C++ の main を実行するコンソールやスクリーンを提供してくれて、 HTML, JavaScript, wasm をホスティングすれば C/C++ のコードがそのままブラウザ上で実行できるよう出力される。私にとっては高機能だがとりあえず C/C++ のコードをどーんと web に持っていきたいならこれで良さそう。 .js を選ぶと、 HTML を除いたものが作成される。私は nodejs で利用したいので今回は .js にしている。.js ファイルは html 上で動作することを前提にしているように見えるので、 nodejs に持っていくには高機能に見えるが面倒ないので一旦このまま使うことにした。(@types/emscripten を利用すると全機能使えそうだが今回は面倒なので見送り)
- --pre-js CMAKESOURCEDIR/src/resources/pre.js−−post−js{CMAKE_SOURCE_DIR}/src/resources/pre.js --post-js CMAKESOURCEDIR/src/resources/pre.js−−post−js{CMAKE_SOURCE_DIR}/src/resources/post.js
前述の .js ファイルの前後になにか書きたいときにはこんな感じでファイルを直接指定できる。今回は空のファイルを置いた。
- -s MODULARIZE -s 'EXPORT_NAME=\"factory\"'
ここが結構なハマりポインツで、どうも HTML とセットの利用用途がメインなのか .js ファイルはもともと他のファイルから利用できない形になっている。そのため、このオプションを指定して .js 内部の Module を作成してくれるファクトリメソッドを外部に公開する必要がある。今回の指定だと export function factory(); が .js ファイルに埋め込まれる。これに気づくまで時間がかかった。ここで factory を指定しているのは意味があって tsembind が生成する型定義ファイルは暗黙にここが factory 指定されている前提に合わせている。
tsembind
npm i -g tsembind
インストールしたら cmake プロジェクトから出てきた .js, .wasm ファイルのあるディレクトリで下記のようにすると標準出力に TypeScript の型定義が出力される。
tsembind YOUR_JS_NAME.js
これでよしなな名前のファイルに書き込めば、私のプロジェクトだと uzumejs.d.ts, uzumejs.js, uzumejs.d.ts の TypeScript から利用するのに必要な 3 ファイルが手に入る。
nodejs
上述で作られたファイルなどをあわせて下記のような形のツリー構造にする。
projectRootDir/
- pkg | + uzumejs.d.ts | + uzumejs.js | + uzumejs.wasm
- src
- index.ts
TypeScript のプロジェクトの構築は下記の感じ。とりあえず動かす用途なので細かい設定はしていない。真面目にパッケージ化するとかやるなら webpack の設定が必要そうだなーと思っているが初心者なので動くのを優先した。
index.ts を下記のようにした。
import uzumejs from "../pkg/uzumejs"
uzumejs().then(uzume => { const contour = new uzume.Contour(1000.0, 2.0); console.log(contour.msLength()); })
shuraba_p@MyComputer:~/projects/uzumejs$ npx ts-node src/index.ts 1002
というわけで C++ で書かれた機能が TypeScript のコードから動いた。やったね。
終わりに
nodejs 初心者なので本当に手間取った。あと私の開発環境古すぎ???問題があってだいぶ更新をすることになった。まあ悪いことではないがちょっと開発自体からおいていかれている感があってつらい。とりあえずこれで nodejs から動かしながら C++ のコードを修正したり、 nodejs 上で GUI 作ったりのスタートラインに立てた。なんかそう書くと全然先が長くて厳しいな。とりあえず現状のものは下記のレポジトリに置いた。久々にきついのをやったが子育てと仕事しながらならこんなもんか。
ポエム
フロントエンドは大変苦手なのだが、まー最近は JavaScript 頑張らなければ、と思いつつもブラウザで何かを作るのはさほど興味がわかなかった。が、ここ数年で C++ のコードを JavaScript の世界に持ってくるのはだいぶスマートになったようで今回は Emscripten 使って過去に作った 今さら C++ で WORLD のラッパーライブラリを書いてみた - フェルマータ というやつを JavaScript の世界に持っていくのを試してみた。
できなかったこと
過去作ったコードは下記のように ExternalProject_add を通して依存性の解決をできるようにしていたが、 ExternalProject_add で作った static library を wasm から利用するのは厳しかった。
ExternalProject_add(uzume_vocoder PREFIX ${CMAKE_CURRENT_BINARY_DIR}/uzume_vocoder GIT_REPOSITORY https://github.com/haruneko/uzume_vocoder GIT_TAG master INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR} CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}" )
emscripten を cmake で利用するためにはオプションに -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake (${EMSCRIPTEN} は emsdk への PATH )的なやつを使わねばならず、これをつけると cmake の add_library がなんのバイナリも作成しなくなってしまい依存性が解決できなくなってしまった。いくつか情報を調べたりしてみたが解決策が思いつかなかったので今回はこの方法は取らないことにした。
やったこと
結局こんな感じの実装にしてみたところ、 JavaScript から C++ のコードが呼べるのを確認できた。 CMakeLists.txt は下記のようにした。
cmake_minimum_required(VERSION 3.1)
project(uzume_emscripten)
include(ExternalProject)
ExternalProject_add(uzume_vocoder PREFIX ${CMAKE_CURRENT_BINARY_DIR}/uzume_vocoder GIT_REPOSITORY https://github.com/haruneko/uzume_vocoder GIT_TAG master INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR} CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}" )
file(GLOB_RECURSE VOCODER_SOURCES ./lib/uzume_vocoder/src/.cpp) file(GLOB_RECURSE VOCODER_HEADERS ./lib/uzume_vocoder/src/.hpp)
include_directories(uzume_emscripten PRIVATE ./lib/uzume_vocoder/src/)
add_executable(uzume_emscripten src/data/ContourBindings.cpp src/main.cpp ${VOCODER_SOURCES} ${VOCODER_HEADERS} )
if (EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".html") endif()
set_target_properties(uzume_emscripten PROPERTIES LINK_FLAGS "-s DEMANGLE_SUPPORT=1 --bind")
結局とっても野蛮なのだが落としてきたコードの install などは完全に無視して、直接ソースコードに依存することにした。もうちょいマシな方法はあるのかもしれないが、ネットの大海にも C++ の情報は少なく、子育てやっている状況で調べられないのでここまでとする。雑に実行した内容は下記の通り。

開発者コンソールでの実行の模様
一つハマりポインツとしてお伝えするのですが下記記事を参考にしたところ、 error: undefined symbol: _embind_register_class (referenced by top-level compiled C/C++ code) がバカスカ出てしまって困ってしまった。
下記記事で LINK_FLAGS ちゃんとするといいよと書かれておりこちらで解決した。まあ今回は本当に雑なのでここまで。