Three.jsで作った夏の海岸。湾曲した砂浜と波打ち際、草原と低ポリの木、きらめく海

ブラウザで歩き回れる海辺を、AIに作ってもらった

前回の記事 で、長年アプリのマスコットだった「えびふりゃ」をAIに3D化してもらいました。最後はブラウザの中を歩き回れるところまで行って、そこで欲が出ました。歩けるなら、世界が欲しい。世界があるなら、遊びが欲しい。 というわけで今回は釣りゲームです。えびふりゃが海岸で竿を投げて魚を釣る。そういうものをブラウザで動かしたい。進め方は前回と同じで、コードはすべてAIに書かせて、私は指示とダメ出しだけ。使ったのはClaude Code(Anthropicのコーディング用AI)のフェイブル5です。技術はThree.js(ブラウザで3Dを描くJavaScriptライブラリ)にしました。UnityのようなゲームエンジンではなくWebを選んだのは、ビルドが要らず、URLを開くだけでスマホからでも確認できて、直しの往復が速いからです。 今回のように「コードは全部AIに書かせて、自分は注文とダメ出しに徹する」進め方は、少し前ならうまく回りませんでした。フェイブル5も登場した当初は評判がいまひとつでしたが、その後の調整でずいぶん使い勝手が良くなっています。一つ頼むたびに手を止めて確認を求めてくるのではなく、一時間規模のまとまった作業をそのまま任せておける。裏でずっと手を動かしていてくれる感覚に近く、細かい指示の往復や不毛なやり取りが減りました。正直、これに慣れるともう元のやり方には戻れません。もっとも、この調子で回すと消費も速く、数日もあればひと区切りの枠を使い切ってしまうくらいには働いてもらっています。 先に言っておくと、釣りの仕組みそのものは数時間でできました。大変だったのはその後です。キャラの体と、そして海。特に海には三度負けました。この記事はそのつまずきの記録です。 「すごいひどいことになってますから」── AIは自分の画面を見ていない 最初の釣りプロトタイプは威勢がよかった。キャスト、アタリ、糸のテンションを見ながらの駆け引き、釣果と表情のリアクション。仕組みは一通り入っています、URLをどうぞ、と来たので開いてみたら、キャラは画面からはみ出すほど巨大で、顔は見切れ、竿は宙に浮いていました。 考えてみれば当たり前で、AIはコードを書いてはいるけれど、レンダリングされた画面を一度も見ていないのです。3D空間のカメラ位置もキャラの大きさも、数値の上でつじつまが合っていれば「できました」と言ってくる。前回のモデリングでは「レンダリングして元絵と重ねて検証してから見せて」という約束で回っていたのに、ゲームになった途端そのループが消えていました。 そこで「自分で画面を確認できるようにしてくださいよ」と頼んだところ、AIはヘッドレスブラウザ(画面を持たないブラウザ)でゲームを開き、スクリーンショットを撮って自分で見る仕組みを作りました。これで開発が一変します。以降のやりとりでは、AIが自分で撮った画像を見て「キャラが地面に埋まっていました、直します」と自己申告してくるようになりました。埋まっているのを見つけるのが私の仕事から、AIの仕事になったわけです。 その仕組みで自己修正を重ねた結果が、いちおう遊べる最初の釣り画面です。長押しでパワーを溜めて離すとウキが飛び、アタリの「!」でタップ、魚が走ったら指を離し、落ち着いたら巻く。釣れれば魚種とサイズが出て、キャラが表情を変えて跳ねる。 操作の「当たり前」は、言わないと伝わらない 次に世界を歩けるようにしました。散歩モードと釣りモードを分け、波打ち際に近づくとキーひとつで釣りが始まる。ここでも細かい攻防があって、たとえば最初の移動操作は「Wで画面の奥へ、Aで画面の左へ」という、カメラ基準の平行移動でした。Dで右を向いたら、進むのはWですよね、という「よくある3Dゲームの操作」は、明示的に言うまで出てきませんでした。 もうひとつ面白かったのはカメラのバグです。キャラが向きを変えると、カメラが背後へ回り込む瞬間に一度ズームインしてしまう。AIに調べさせると、カメラの位置を直線で補間していたのが原因でした。円弧の上を回るべきところを直線で近道するので、旋回中だけキャラに寄ってしまう。角度を補間するよう直して解決です。言われてみればそれはそうだ、という話ですが、こういう「触ると気持ち悪いが、コード上は間違っていない」類のバグは、実際に触った人間が指摘しないと直りませんでした。 お手本のURLを渡したら、世界が変わった このころの見た目は、砂色の床と青い板が並んでいるだけの、いかにも工作然としたものでした。言葉で「もっと綺麗に」「夏っぽく」と言っても、良くなる気配がない。そこで方針を変えて、私が理想とする世界観のWebGL作品、Vicente Lucendo氏のSummer Afternoon のURLをそのまま渡しました。「このような世界観でできないものなの?」と。 効果は劇的でした。AIは作品を特徴ごとに分解して──暖色のパレット、グラデーションの空、風でなびく大量の草、様式化された水、柔らかい光──それを一つずつ実装に落とし始めました。百回言葉で注文するより、見本をひとつ渡すほうが早い。人間のデザイナーに頼むときと同じでした。 余談ですが、このSummer Afternoon、ただ眺めるだけでなく歩き回って遊べて、マップのどこかに秘密が5つ隠されています。探しはじめると止まらなくなって、私は4つまで見つけたものの、最後のひとつがどうしても見つかりませんでした。この世界に憧れて今回の海辺づくりが始まったので、スクリーンショットを貼るより、ぜひリンクから本物を歩いてみてほしいです。開くだけですぐ動きます。 人間キャラは、木偶にしかならなかった 世界観を変えるにあたって、キャラは「無理にえびふりゃじゃなくてもいい、少年にしよう」ということになりました。麦わら帽子の男の子が海辺で釣りをする。いいじゃないですか。 ところが、これが今回いちばんはっきりした失敗になりました。AIは円柱や球を組み合わせて少年を作り、関節の位置でパーツを回して歩かせようとします。出てきたものは、腕と脚の生えた木の人形が滑っていくような、見ていて萎える動きでした。おまけに一度は頭が丸ごと消える事故まで起きています(内部の座標変換が壊れて、頭のグループだけ描画されなくなっていました)。 AIに「抜本的に品質を上げるにはどうすればいいか」と正面から聞いたところ、返ってきた答えは率直でした。人型が自然に動いて見えるためには、骨格(リグ)と、骨に皮膚が追従して変形する仕組み(スキニング)と、ちゃんと作られた歩行アニメーションの三点セットが必要で、これはプリミティブの寄せ集めをコードでどう頑張っても埋まらない。つまり自作をやめて、リグとアニメーションが最初から入っているモデルを使うべきだ、と。 ライセンスを調べさせると、これがなかなか勉強になりました。AdobeのMixamoは商用無料だけれど素材単体の再配布が禁止で、ブラウザゲームは配信ファイルが誰でもダウンロードできてしまうためグレー。一方Quaternius などのCC0(実質パブリックドメイン)素材なら、商用でも帰属表示なしで完全に自由。Webで出すならCC0一択という結論です。 ちなみに、「無料で自由に使えるキャラクター」の代表格としてくまモンを思い浮かべる人もいるかもしれませんが、あれはCC0ではありません。くまモンの著作権は熊本県が持っていて、商品や広告など商用で使うには県への利用許諾の申請が要ります(許可されれば利用料はかからず、熊本のPRになることが条件です)。一方で、個人が非営利で楽しむぶんには申請もいらず、SNSや個人ブログへの掲載などかなり自由に使えます。CC0の「誰でも無条件・無申請で完全に自由」とは似て非なるもので、あの気前のよさは権利を手放しているのではなく、熊本県が許諾制度でうまく開放しているからなんですね。だからこの記事にも、くまモンの画像そのものは載せていません(この記事は自作アプリの宣伝も兼ねているので、勝手に使うなら許諾が要る側です)。 ひとまず確実に入手できたCC0のロボット(Three.js公式サンプルのRobotExpressive。歩き・待機・ジャンプなどのアニメーション入り)を組み込んでもらいました。移動すれば歩きへ、止まれば待機へなめらかに切り替わり、釣果が出ればワンショットで喜ぶ。木偶人形との差は歴然でした。少年の見た目は失いましたが、「動きの質は素材で買う」が正解だと納得した瞬間です。 木も草も、自作をやめてフリー素材に頼る キャラで学んだことは、そのまま背景にも効きました。AIが球と円柱で作った木はどう見てもおもちゃで、草に至っては「尖った針がゆらゆら動いている」ようで気持ち悪い。ここもQuaterniusのSimple Nature(CC0)に差し替えることにして、私がサイトからFBXファイルをダウンロードし、AIに渡しました。 ここで小さな学びがふたつ。ひとつは形式の話で、配布されていたのはFBX・OBJ・Blenderファイルの3種でしたが、Web(Three.js)で使うならglTF/glbが本命、なければFBXをBlenderで変換すればよく、OBJは避ける、.blendは自分で編集しない限り不要。つまり「今後はglbかFBXだけ落とせばいい」。 もうひとつは罠の話。FBXをglbに変換して組み込んだのに、木がまったく表示されない。AIが調べたら、変換の過程でマテリアルの不透明度が0(完全に透明)になっていました。木は透明人間として、ちゃんとそこに立っていたわけです。不透明に戻して一件落着。草は1本のモデルを1万5千本、インスタンシングという手法でまとめて描画し、シェーダーで根元を固定したまま穂先だけ風に揺らしています。 海との戦い その一 ── 画面が真っ黒になる呪い さて海です。私はこのゲームの主役は海だと思っています。普段海を見ない人が、画面の中の海を見てちょっと嬉しくなる。そういうものにしたかったので、「キラキラ水面に光る波、太陽の感じ、空の綺麗な青。ここは絶対に維持してほしい」と注文しました。 AIは水面のシェーダー(画素ごとの色を計算するプログラム)を書き、太陽へ向かう反射帯にきらめきを集める表現を作りました。これ自体は綺麗にできたのですが、その過程で妙な事故が続きます。発光表現のためのブルームという後処理を入れた途端、画面全体が真っ黒になるのです。 原因究明は探偵ものみたいで面白かった。AIは容疑者を一つずつ消していきました。花粉のパーティクルの計算に数値エラー(NaN、非数)を見つけて直す──まだ黒い。水面を消してみる──黒いまま。後処理を切る──直った。つまりブルームが、シェーダーのどこかで生まれるNaNを画面全体に塗り広げていた。数値のエラーが一画素でもあると、ぼかし処理がそれを全画面に拡散してしまうのです。結局ブルームは諦めて、きらめきはシェーダー側で表現することになりました。 海との戦い その二 ── 水際のチートは、ぜんぶバレる 次は波打ち際です。最初の実装は、砂浜と海の境界に白い帯を一本置いただけのもので、「境界線が一直線。こんなことありえない」と私が却下。ではとAIが持ってきたのが、水面の手前の縁だけをくねくね波打たせる方式でした。 これが今回いちばんの見ものだったかもしれません。スクリーンショットを撮ると、波の先端が蛇のようにうねっているだけで全然波じゃない。しかも波の先端と海本体の間に、なぜか砂浜が見えている。水が分裂しているわけです。私が写真を突きつけると、AIは「チートがバレましたね」と白状して、根本からやり直すことになりました。 やり直しの方針は、ごまかしをやめることでした。砂浜を、実際に海へ向かって下る斜面として作る。波で上下する水面がその斜面と交わる線が、すなわち波打ち際になる。水際線を「描く」のではなく、地形と水面の交差から「生まれさせる」わけです。こうすると、うねりで水面が持ち上がれば水際は自動で砂浜側へ押し寄せ、引けば濡れた斜面が現れる。水と砂の間に隙間ができようがありません。 この方式に変えた直後、もうひとつ豪快なバグが出ました。海と砂浜の間に、幅の広い謎の白帯が現れたのです。調べたら、地面のポリゴンが波打ち際の手前で終わっていて、引き波のときに水際がその先へ下がると、そこには地面が存在せず、透けた水の向こうに背景の空が見えていた。白帯の正体は泡ではなく「世界の穴」でした。地面を海の中まで延長して海底を作り、ついでに浅瀬は水深に応じて水を透明にしたら、砂→濡れ砂→砂が透ける浅瀬→青、という本物の海辺のグラデーションになりました。 おまけに、海底ができたことで副産物がありました。それまでキャラは波打ち際までしか歩けなかったのですが、地面が海中まで続いたので、そのまま海に入って腰まで浸かれるようになったのです。 ...

公開: 2026年7月21日 · Toshihiko Arai
連載『手で動かすAI』第1回のカバー。ニューロン1個の構造(入力×重み→重み付き和→活性化→出力)と、平面上の2グループを分ける境界線のイラスト

ニューロン1個は何をしているのか ── 重みで混ぜて、境界線を1本引く

前回 は、散らばった点に直線を1本当てはめ、つまみ(傾きと切片)を回して誤差を減らすところまでをやりました。「学習とはつまみ回しである」という、連載全体の背骨です。今回はそこから一歩進んで、いまのAIをかたち作っている最小の部品、ニューロンを1個だけ取り出して、その中で何が起きているのかを見ます。名前はいかめしいですが、やっていることは前回とほとんど地続きです。 ニューロンは「重みで混ぜて、発火するか決める」だけ ニューロンは、いくつかの入力を受け取って、一つの出力を返す小さな部品です。中身は3ステップしかありません。 まず、それぞれの入力に重みをかけます。重みは、その入力をどれだけ重く見るかを決める数で、前回のつまみ(傾き)を入力の数だけ増やしたものだと思ってください。次に、重みをかけた入力を全部足し合わせ、そこにバイアスという下駄を1つ加えます。これで重み付き和という一つの数になります。最後に、その数がある基準を超えたかどうかで出力を決めます。この最後のひと押しを活性化と呼びます。いちばん素朴な活性化は、0を超えたら1(発火)、超えなければ0、というステップ状の切り替えです。 式にすると、これだけです。入力の数だけ「重み × 入力」を足して、バイアス $b$ を加える。 $$z = w_1 x_1 + w_2 x_2 + \cdots + b$$ そしてニューロンの出力は、 $$y = \text{活性化}(z)$$ 重み $w$ とバイアス $b$ が、このニューロンのつまみです。入力 $x$ は与えられたデータなので動かせませんが、つまみは学習で回せる。ここが前回とまったく同じ構図です。 ニューロン1個 = 平面に引いた1本の線 つまみが何をしているのかは、入力が2つの場合を絵にすると一目で分かります。ただ、その前に抽象的な話を一度、地面に下ろしておきます。 これから出てくる点の一つひとつは、判断したい対象を1個だと思ってください。たとえば果物の仕分けなら、点1つが果物1個です。横軸 x1 をその大きさ、縦軸 x2 を甘さとすれば、点の位置は「その果物の大きさと甘さ」を表します。そして点の色が正解で、青はりんご、赤はみかん、というぐあいです。x1 や x2 のような入力は、対象から測った特徴のことだと考えると腑に落ちます。入力が3つなら、測る特徴が3つに増えるだけです。 ニューロンがやりたいのは、この大きさと甘さから「どっちの果物か」を当てることです。重み w1 は、その判断で大きさ(x1)をどれだけ重視するかを決めるつまみだと考えてください。w1 を大きくすれば大きさで強く決め、小さくすれば甘さ(x2 とその重み w2)の方を重く見ます。この2つの重みとバイアスで、平面上に1本の境界線が決まり、その片側を青、反対側を赤と判定します。線の向きは重みが、位置はバイアスが決めます。 言葉より動かした方が早いので、デモを用意しました。青と赤の点を、ニューロン1個の境界線で分けてみてください。 うまく表示されないときは、デモを別タブで開く 。 3つのつまみ(重み2つとバイアス)を動かすと、平面の塗り分け、つまりニューロンの判定と、境界線が動きます。目標はギリギリを狙うことではなく、青が片側・赤が反対側にそろって、まちがいが0になることです。それさえ満たせば、線の引き方はどれでも正解です。手で合わせてもいいですし、「自動で学習」を押せば、前回とまったく同じ勾配降下が、まちがいの減る向きへ3つのつまみを回して境界線を運びます。線を引く部品が、線の引き方を自分で見つける。これがニューロン1個の学習です。 ここで当然の疑問が浮かびます。もし青と赤が、きれいに片寄らずぐちゃぐちゃに混ざっていたら、どうなるのでしょう。答えは、1本の線ではどうやっても分けられない、です。これはデモの不備ではなく、ニューロン1個の正真正銘の限界です。まっすぐ1本の線で二分できる並び(専門的には線形分離可能といいます)しか、ニューロン1個では扱えません。そして、この「1本では割れない並び」を扱えるようにする工夫こそが、次回への入り口になります。 中身のコード ニューロン1個の計算は、本当にこれだけです。飾りのデモ用ではなく、いまのAIの内側でも同じ計算が延々とくり返されています。 // 入力 x1, x2 を重み w で混ぜ、バイアス b を足す const z = w1 * x1 + w2 * x2 + b; // 重み付き和 // 基準を超えたら発火(ステップ活性化) const y = z > 0 ? 1 : 0; 学習は前回と同じで、まちがいが減る向きへ w1, w2, b を少しずつ動かすだけです。ひとつだけ実用上の都合を挟むと、上のステップ活性化は超えたか超えないかでカクッと切り替わるため、勾配(傾き)が測れません。そこで実際にはもっと滑らかな活性化を使います。このデモでも、なめらかなS字を描くシグモイドという関数を活性化に使って、勾配降下が効くようにしています。活性化の種類の話は、あとの回でもう少し掘り下げます。 ...

公開: 2026年7月21日 · Toshihiko Arai
オクターブ違いの成分が釣鐘型の音量の山の上を滑り、上端で消えて下端から生まれ直すことで無限に上がり続ける音階になることを示す図

無限に上がり続ける音階 ── シェパードトーン

階段を上がっているのに、いつまでも同じ階に戻ってきてしまう。エッシャーのだまし絵に出てくるあの無限階段を、音で作ったらどうなるでしょうか。実は本当に作れます。ドレミファソラシド、と半音ずつ確かに上がり続けているのに、1分聴いても10分聴いても、いっこうに「一番上」に着かない音階。シェパードトーンと呼ばれる音の錯覚で、1964年に心理学者ロジャー・シェパードが計算機で合成して見せたものです。 この連載ではこれまでに差音 とミッシング・ファンダメンタル 、2つの「鳴らしていない低音が聞こえる」錯覚を扱ってきました。今回はその棚の3本目です。前の2本が耳の作る幻の低音だったのに対し、こちらは耳が音の高さをどう測っているかの隙を突く錯覚で、種明かしを知ると、プログラムでの作り方まで一直線につながります。 上がり続けるには、どこかで戻るしかないはず まず、なぜこれが不思議なのかをはっきりさせておきます。音の高さを切れ目なく滑らかに変えることをグリッサンドといいますが、サイレンのように1本の音をグリッサンドで上げ続けると、いつかは必ず限界が来ます。楽器の音域の上限か、耳に聞こえる周波数の上限か、いずれにせよ天井はある。上がり続けたければ、どこかで下へ戻るしかありません。そして戻った瞬間の「ヒュッと落ちる」は、誰の耳にもはっきり聞こえてしまいます。 シェパードトーンの答えは、右の図のほうです。音を1本ではなく、1オクターブずつ離した何本もの成分の束にする。そして「戻る」瞬間を、音量がゼロになる端っこに隠してしまう。落ちる音が聞こえないのは、落ちるときにはもう聞こえない小ささになっているからです。 この仕掛けが成立するには、耳の側にも条件がひとつ要ります。それが次の話です。 耳は「どのオクターブか」を音量の重心で聞いている 平均律の回 でやったとおり、1オクターブ上の音とは周波数がちょうど2倍の音のことです。そして人間の耳にとって、オクターブ違いの音はただの「別の高さ」ではありません。ピアノにドは8つほどありますが、どれを弾いても私たちは同じ「ド」だと感じます。音の高さには、ドかレかソかという音名の顔と、それが低いドか高いドかというオクターブの顔、2つの顔があるわけです。 ここで、同じ音名のドを、低いドから高いドまで全オクターブぶん一度に鳴らしたらどうなるか。音名のほうは文句なしに「ド」です。ところがオクターブのほうは、材料が全部の高さに散らばっているので、決め手がありません。このとき耳は、成分の音量がどのあたりに集まっているか、いわば音量の重心で「だいたいこの高さのド」と聞き取ります。逆に言えば、重心さえ動かなければ、中身の成分がこっそり入れ替わっても、耳には同じ高さ帯の音に聞こえ続けるということです。シェパードトーンは、この性質を正面から突きます。 山は動かさず、成分だけが山を滑っていく 作り方はこうです。1オクターブおきに並べた成分たちに、周波数に応じた音量の山を掛けます。山は釣鐘型で、真ん中の高さ帯がいちばん大きく、低すぎる端と高すぎる端ではちょうどゼロ。つまり両端は無音です。この山は最後まで固定しておいて、成分たちだけを全員一斉に、同じ速さで上へ滑らせます。 図の赤い1本を追ってみてください。出発時に上のほうにいた成分は、上がるにつれて山を降り、どんどん小さくなりながら右端へ抜けて消えます。同時に左端では、新しい成分が無音のまま生まれて、山を登りはじめる。そして全員がちょうど1オクターブ上がりきった瞬間、成分の配置は出発時と寸分たがわず同じになります。1本1本は確かに1オクターブ上がったのに、スペクトル全体としては振り出しに戻っている。音量の重心は、最初から最後までぴくりとも動いていません。 あとはこれを繰り返すだけです。どの瞬間を切り取っても、すべての成分は本当に上昇しているので、耳は「上がっている」と正しく感じ続けます。一方で、1オクターブ上がるごとに全体は元どおりになるので、いつまで経っても高くはならない。配役の入れ替えは毎回、音量ゼロの端で行われるため、耳には検出のしようがありません。床が下り続けるらせん階段を、延々と登らされているようなものです。 触ってみる この入れ替わりは、目で見ると一段と腑に落ちます。釣鐘の山の上を8本の成分が滑り続け、右端で消えた成分が左端から生まれ直すデモを用意しました。なめらかに滑る連続版と、半音ずつ階段で上がる版を切り替えられます。 うまく表示されないときは、デモを別タブで開く 。 おすすめは、鳴らしっぱなしにして「スタートからの上昇量」の数字を眺めることです。ピアノの全音域は7オクターブと少ししかないのに、この音階は10オクターブでも20オクターブでも平気で上がり続けます。もちろん音はずっと同じ高さ帯のまま。それから、向きを下降に切り替えると、今度は永遠に落ち続ける音になります。エレベーターの下りが終わらない感覚も、なかなかのものです。 この音、聴き覚えのある方も多いはずです。映画『ダンケルク』では、ハンス・ジマーがこの原理を音楽全体に織り込み、終わらない上昇で緊張がひたすら高まり続ける感覚を作りました。ゲームでは『スーパーマリオ64』の無限階段が有名で、登っても登っても続く階段の演出に、上がり続ける音階がぴったり重ねられています。錯覚と分かっていても、心拍のほうは正直に釣られてしまうのが面白いところです。実際にどんなふうに使われているかは、下から聴いて確かめてみてください。 映画『ダンケルク』 / ハンス・ジマー Supermarine スコア全体がシェパードトーンで組まれた作品ですが、単曲なら「Supermarine」が代表例です。上がり続ける音でひたすら緊張が高まっていく感覚が、はっきり味わえます。 YouTubeで探す Amazonで探す ゲーム / スーパーマリオ64 無限階段のBGM 登っても登っても終わらない階段の演出に、上がり続ける音階が重ねられた有名な例です。 YouTubeで探す Amazonで探す ところで、この錯覚の土台にあった「音の高さには音名とオクターブの2つの顔がある」という性質は、音感を鍛えるときにもそのまま顔を出します。ドの音名は当てられるのにオクターブを取り違える、というのはよくあることです。私事ですが、筆者は音を聴いて当てる練習ができる音感トレーニングアプリ Harmonize を個人開発しています。耳がどうやって高さを聞いているのかを知ってから練習すると、また違った面白さがあります。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play どう実現するか デモの中身は、倍音の回 と同じくサイン波を並べる加算合成で、違いは周波数と音量を動かし続けることだけです。成分を $N$ 本(デモでは8本)用意し、いちばん下の周波数を $f_{\min}$ とすると、位置 $u$(0〜$N$ オクターブ)にいる成分の周波数と音量は $$f = f_{\min} \cdot 2^{u}, \qquad a(u) = \tfrac{1}{2}\left(1 - \cos\tfrac{2\pi u}{N}\right)$$ で決まります。$a(u)$ が例の釣鐘で、$u=0$ と $u=N$ の両端でちょうどゼロになります。あとは全成分の $u$ を同じ速さで増やし続け、$N$ を超えたら 0 に巻き戻すだけです。 ...

公開: 2026年7月21日 · Toshihiko Arai
平面イラストのマスコット(えびふりゃ)と、それを3D化したレンダリングを矢印で並べた図

平べったいマスコットを、AIに3D化してもらった ── イラストの輪郭をそのまま回転体にする

音楽アプリのマスコットに、オレンジ色の丸っこいキャラがいます。鉢巻きをして、後ろにエビの尻尾がちょこんと付いた「えびふりゃ」。このキャラはずいぶん長い付き合いで、いまの音楽アプリ「Harmonize」よりも前、えびふりゃだけが出てくる別のアプリのために描いたのが最初でした。もうずいぶん前のことです。平面のイラストとしてはずっと気に入っていて、いつか立体にしてみたいという気持ちも、かなり前からありました。 やらなかった理由は、単純に面倒くさかったからです。以前3Dプリンターを持っていた頃にBlenderでモデリングをしたことがあって、正方形の箱ひとつ作るのにも骨が折れた記憶があります。実物として印刷するとなると、ミリ単位で寸法を合わせないと困る。その調整のやり方を覚えるのが一番大変でした。丸や四角のパーツをぽこぽこ足していくだけなら本来そう難しくはないのでしょうが、ソフトに慣れる手間すら億劫で、ずっと手をつけずにいました。 今回それをやってみたのは、AIの時代なら、自分ではBlenderを操作せずに3Dモデリングができるのではないか、と思ったからです。実際、この記事で紹介する立体は、モデリングのためのコードをすべてAIに書かせて、私は「こうしてほしい」と指示を出し、出てきた結果を見て直しを頼む、という進め方で作りました。私が3D画面でポリゴンをこねた場面は一度もありません。丸1日、並行で他の作業をしながら、AIと会話しているうちにできあがっていた、という感覚に近いです。 長いあいだ、平べったいまま磨いてきた 先に、このキャラの見た目の変遷を並べておきます。いちばん左が初代。フラットで、目も点のような、いかにも手描きらしい素朴な顔です。真ん中が、その後じわじわ手を入れて、いまアプリに入っている質感のついた現行デザイン。鉢巻きに立体感が出て、ほおのぼかしや光沢が加わっています。そして右が、今回3Dにしたもの。ずっと平面のまま磨いてきたキャラが、はじめて立体になった格好です。 初代を立体にしなかったのは、繰り返しになりますが面倒くさかったからです。素朴なフラット絵を3Dにしても、たぶんそれはそれで可愛いのですが、当時はそこに手をかける気になれなかった。それが長い時を経て、平面の集大成である現行デザインからいきなり立体へ飛べたのは、間に入ってくれるAIがいたからです。 いちばん苦労したのは、楕円をやめさせることだった 意外なことに、難所は立体の作り方そのものではありませんでした。AIに意図を伝えることのほうでした。 「丸いマスコットを立体にして」と頼むと、AIはごく素直に、楕円体で近似しようとします。横から見たシルエットに楕円をあてはめれば、正面はそれらしく見える。ところが少し回すと、元のイラストが持っていた「下がぷっくり膨らんで上がすぼまる」あの肉まんのような愛嬌が消えて、のっぺりした卵になってしまう。人の顔でいう骨格が違うようなもので、色や表情をどれだけ寄せても「似ているけれど別人」から抜け出せません。 厄介なのは、これがなかなか言葉で伝わらないことでした。「楕円じゃない」「もっとイラストのままの形に」と言っても、卵型の関数に変えてみたり、輪郭からの距離で膨らませてみたりと、AIは別の「きれいな近似」を次々に持ってくる。どれも一長一短で、正面だけ合って側面が痩せたり、妙な稜線が縞になって出たり。汎用のなめらかな形へ寄せにいくほど、元の絵の個性が削れていく。この「近似から離れさせる」ところに、何度もやりとりを重ねました。 効いた一手 ── イラストの輪郭を、画像から読み取ってそのまま回す 行き詰まりを抜けたのは、発想を逆にしてからでした。楕円という「きれいな形」に絵を近づけるのをやめて、イラストの輪郭そのものを立体の材料にする。 やっていることは拍子抜けするほど単純です。まず元のイラスト画像から、マスコットのシルエットを画素単位で拾い出します。塗ってある範囲のふちをたどって、輪郭の点の並びを取り出すわけです。次に、その輪郭の右半分だけを使い、「中心の軸からどれだけ横に張り出しているか(半径)が、高さごとにどう変わるか」というプロファイルとして扱う。あとはこの一本の曲線を、縦の軸のまわりにぐるりと360度回すだけです。ろくろで壺をひくのと同じで、断面の曲線が決まれば立体が決まります。 大事なのは、この断面が楕円のような「作った形」ではなく、イラストから測り取った実物の輪郭だ、という点です。近似して立体をこしらえるのではなく、絵の輪郭を測って、測ったものを回す。この切り替えが効きました。 この作り方には、素直だからこその強みが三つあります。正面から見た形と真横から見た形が必ず一致すること。真上から見ると完璧な円になること。そして何より、断面がイラストの実際の輪郭なので、あの下ぶくらみの愛嬌がそのまま立体に乗ることです。回転体だから左右対称になりますが、退屈さの正体は左右対称そのものではなく、断面を楕円のような当たり障りのない形にしてしまうことのほうにありました。断面が絵から来ていれば、対称でも十分にキャラの顔になります。 尻尾の突起は左上にはみ出しているので、輪郭の右半分だけを使えば自然に無視できる、というのも都合がよいところでした。この「回してできた本体」が、以降のすべての土台になりました。 顔のパーツは「絵から座標を測って」載せる 本体ができたら、次は鉢巻き・目・眉・口・ほおの赤み、といった顔の要素です。ここでも方針は一貫していて、パーツの位置や大きさを勘で置くのではなく、元のイラストから一つひとつ測って決める、というやり方をAIに徹底させました。 たとえば目なら、イラストの中の「丸くて大きい黒い塊」を二つ探し、その中心と半径、白いハイライトの位置を数値として読み取る。読み取った座標を本体の表面に対応づけ、そこに少しつぶした黒い球を置きます。鉢巻きは、帯の色をした画素の並びから傾きと幅を測り、本体の表面に沿った帯として巻く。「えびふりゃ」の文字は帯のテクスチャとして貼りました。口や眉のような細い線は、中心を通る曲線と太さを測って、表面に浮かせた細いリボンとして再現しています。 測った値は、キャラごとに一つのデータとしてまとめておきます。すると立体を組み立てる側は、そのデータを読んでパーツを並べるだけの役割に徹することができる。元絵を差し替えて測り直せば、同じ仕組みが別のキャラにもそのまま使える、という見通しも同時に立ちました(これが後半で効いてきます)。 出来上がりが元絵とどれくらい合っているかは、毎回きちんと確かめました。3Dのレンダリングを元のイラストに半透明で重ねて、目や口や鉢巻きの位置がずれていないかを目で見る。数字が合っていても、重ねてみると微妙に印象が違う、ということはよくあります。下の画像は、3Dの顔を元絵にぴたりと重ねたところです。 正面が合っていても油断はできません。斜めから見たときに目のハイライトがずれる、細いリボンが表面から浮きすぎて影が出る、といった立体ならではのズレは、正面の重ね合わせだけでは見つからない。角度を変えたレンダリングを何枚も見比べて、少しずつ詰めていきました。 平面の「記号」を、立体の都合に合わせて翻訳する この作業でいちばん面白かったのは、平面の絵では成立していた表現が、立体にすると急に不自然になる場面でした。 いい例が眉です。元のイラストは、片方の眉だけを描いています。平面の「表情記号」としてはそれで十分伝わるのですが、実体のある立体で片眉だけだと、反対側がのっぺりして落ち着きません。そこで、描かれている片方の眉を、顔の中心線を基準にもう片方へ折り返して両眉に増やしました。単純な左右反転ではなく、顔がやや斜めを向いた構図であることを考えて、立体の表面に沿う形で折り返すのがコツでした。 尻尾も同じ話です。エビの尻尾は、イラストでは体の後ろからちょこんと出た羽根のように描かれていますが、正体は鉢巻きの結び目です。頭の後ろに縛ったこぶがあって、その結び端が尻尾に見えている。3Dでは律儀にそう作りました。後ろに回り込んだときに「ちゃんと縛ってある」感じが出ると、平面では気づかなかった立体らしさが生まれます。 ほおの赤みのような、輪郭を持たないぼかしも悩みどころでした。これは形として作るのではなく、本体の色そのものを、ほおの位置からじわっと赤側へにじませることで表現しています。表面の色として塗っているので、後でジャンプさせて体が伸び縮みしても、赤みがちゃんと一緒に動いてくれます。 表情を切り替える 元のアプリには、マスコットの表情がいくつか用意されています。ふつう顔、困り顔、目がキラキラの大喜び、にっこり、口笛。せっかくなので、これらを3Dでも切り替えられるようにしました。 仕組みは本体と同じで、各表情のイラストから顔パーツを測り、表情ごとのデータとして持っておく。あとは組み立てるときに「どの表情か」を指定すれば、対応するパーツに差し替わる。文字どおり、顔だけを付け替えているイメージです。 ここでも平面から立体への翻訳が何度も出てきました。困り顔の汗は、貼り付いた水滴ではなく、頭から飛び出して弧を描いて消える飛沫として作る。大喜びの目のキラキラは、平面の星マークを立体の光として置き直す。口笛のすぼめた口は、平面では単純な形ですが、立体でカメラが横に回り込むと向きの違和感が出るので、視点に応じて向きが変わるようにしています。どれも「元の絵が伝えたかったこと」を、立体の都合に合わせて訳し直す作業でした。 ジャンプさせる 止まっているだけでは寂しいので、ぴょんぴょん跳ねてもらいました。物理シミュレーションではなく、手付けのアニメーションです。 跳ねるときの気持ちよさは、踏み込みでぺしゃっとつぶれ、飛び出す瞬間に縦へ伸び、空中で丸に戻り、着地でまたつぶれる、という「つぶれと伸び」のメリハリから来ます。アニメーションの世界で昔から使われる原則で、このとき縦に伸ばしたぶんは横を細く、つぶしたぶんは横を太くして体積が変わらないようにすると、ゴムまりのような弾力が出ます。 面白いのは、ジャンプと表情を組み合わせたときです。踏み込みまではふつうの顔で、飛び出す瞬間に目をキラキラさせて口を開ける。そうやって動きと表情のタイミングを合わせると、ただの丸い物体が急に生き物らしくなります。 ちなみに、このマスコットが平面のまま暮らしている音楽アプリはこちらです。宣伝を少しだけ。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play 二匹目は、ほとんど設定を足すだけだった このマスコットには兄弟がいます。ミントグリーンの鉢巻きをした「かきふりゃ」。えびふりゃで作った仕組みが本当に汎用なのかは、二匹目で試されます。 結論からいうと、拍子抜けするくらい素直に立体になりました。元絵を差し替えて、輪郭と顔パーツを測り直す。あとはキャラごとの違い ── 鉢巻きが緑であること、尻尾はなくて結び目のこぶだけがあること、ほおの代わりに星のイヤリングを下げていること ── を、設定として書き足すだけです。同じパイプラインが、そのまま二匹目を吐き出しました。 ...

公開: 2026年7月20日 · Toshihiko Arai
連載『手で動かすAI』第0回のカバー。見出し『AIは、どうやって学んでいるのか ── つまみを回して、誤差を減らすだけ』と、パラメータのつまみ・誤差の表示・点群に当てはめた直線のイラスト

AIは結局どうやって学んでいるのか ── つまみを回して誤差を減らすだけ

ディープラーニングという言葉が一気に広まったのは2010年代の半ばでした。写真に何が写っているかを機械が言い当て、囲碁で人間の頂点に勝つ、といったニュースが立て続けに流れたころです。その勢いに、「なんだか急に難しそうな世界になったな」と距離を置いてしまった人は少なくないはずです。分厚い入門書を開いてはみたものの、最初の数十ページで数式と専門用語に力尽きた、という記憶を持つ人もいるかもしれません。 この連載「手で動かすAI」は、その距離をゼロから詰め直すために始めます。難しさから逃げるつもりはありません。数式もコードも、イメージがはっきりするところでは遠慮なく出します。大事にするのは順番のほうです。まずブラウザでその場で触れる小さなデモで、何が起きているのかを目で見て手で動かす。そのうえで、それを言い表す式やプログラムを見にいく。数式が先に立ちはだかって心が折れる、というありがちな挫折の順序を、ひっくり返すのが狙いです。行き先は、いま話題の大規模言語モデル、つまり ChatGPT のように文章を書くAIの仕組みまで。遠回りに見えても、土台からの一本道でそこまでたどり着きます。 その入り口として、この回では連載全体を貫くたったひとつの考え方をつかみます。AIが「学習する」とは、そもそも何をしていることなのか、です。 学習の正体は「つまみ回し」 先に結論を言ってしまうと、機械が学習するというのは、正解に近づくようにモデルのつまみを少しずつ回しているだけです。魔法でもなければ、機械が自分で意思を持って考えているのでもありません。 図の真ん中にある箱がモデルです。中身はデータに当てはめるための関数で、いくつものつまみを持っています。このつまみは、専門的にはパラメータと呼ばれる、モデルの振る舞いを決める調整ネジのことだと思ってください。左から入力データを入れると、つまみの今の位置に応じて、右から予測が出てきます。 その予測を正解と並べて、どれだけ外したかを一本の数字にまとめます。これが誤差です。あとはその誤差が小さくなる向きへ、つまみをほんの少しだけ回す。回してはもう一度予測し、また誤差を見て回す。この地味なくり返しこそ、私たちが学習と呼んでいるものの正体です。 つまみが2つなら単純な直線しか引けませんが、つまみを何百万、何十億と増やしていけば、複雑きわまりない関数も表せるようになります。いまのAIが桁外れに賢く見えるのは、このつまみの数と、回した回数がとほうもなく大きいからで、やっていること自体は最後までこの図のままです。 触って確かめる 言葉だけではピンと来ないので、実際に動かせるデモを用意しました。散らばった点に、1本の直線をできるだけうまく当てはめる遊びです。 うまく表示されないときは、デモを別タブで開く 。 つまみは2つ、直線の傾きと切片です。動かすと、点から直線へ伸びるオレンジの縦線が伸び縮みします。これが一点ごとの外し具合です。大きく外した点ほど強く効くように、この外し具合を二乗してならした一つの数字が、上に出ている誤差です。まずは手で2つのつまみを探って、誤差をできるだけ小さくしてみてください。 そのうえで「自動で学習する」を押すと、まったく同じことを機械がやります。誤差が減る向きを計算して、つまみを少しずつ回していく。数字がするすると下がり、直線が点の真ん中へ吸い付いていくはずです。人が勘で合わせても、機械が自動で合わせても、起きているのは同じつまみ回しなのだと腑に落ちれば、この回の目的は達成です。 「自動で学習」の中身をのぞく そのボタンの裏で動いているのは、勾配降下法という短いプログラムです。名前だけで済ませず、せっかくなので中身をのぞいてみましょう。これは飾りのデモ用アルゴリズムではなく、いまのAIを訓練しているまさにその仕組みだからです。 やることは2段階だけです。まず今のつまみの位置で、各点をどれだけ外しているかを見て、「このつまみをほんの少し増やしたら、誤差は増えるのか減るのか」をつまみごとに求めます。これが勾配で、坂の傾きのようなもの、誤差の丘のどちらが下り坂かを教えてくれる値です。次に、その下り坂の向きへつまみを少しだけ動かす。これだけです。デモの「自動で学習する」は、次のコードをくり返しているだけです。 // 1. 今のつまみ a, b で、各点の外し具合から「勾配」を求める let ga = 0, gb = 0; for (const [x, y] of データ) { const r = a * x + b - y; // 予測 − 正解(=どれだけ外したか) ga += 2 * r * x; // つまみ a を動かしたときの誤差の傾き gb += 2 * r; // つまみ b を動かしたときの誤差の傾き } ga /= n; gb /= n; // 2. 誤差が減る向き(下り坂)へ、つまみを少しだけ動かす a = a - 学習率 * ga; b = b - 学習率 * gb; 肝は最後の2行です。勾配は「増やすと誤差が増える向き」を指しているので、マイナスを付けて逆向き、つまり下り坂へ動かします。動かす幅を決めるのが学習率で、大きすぎると行き過ぎて発散し、小さすぎると亀の歩みになります。この2段階を何百回とくり返すと、つまみは誤差がいちばん小さい谷へ落ち着きます。 そして肝心なのはここです。いま見た数行は、写真を見分けるAIも、文章を書く大規模言語モデルも、訓練しているまさに中核です。違うのは、つまみが2つではなく何億個もあること、そして膨大なつまみの勾配を効率よく求めるための誤差逆伝播という工夫が加わること。骨格はこのままです。だから「勾配なんとか法」は、AI開発に関係ないどころか、その心臓そのものだと言えます。連載では専用の回で、この勾配を数値微分から手で組み立て、逆伝播まで一段ずつ作っていきます。 この一点から、全部が伸びていく これから先、話はニューロン、ニューラルネットワーク、画像認識、そして言葉を扱うAIへと広がっていきます。名前も見た目もどんどん複雑になりますが、根っこはずっと同じです。誤差を測り、それが減る向きへつまみを回す。大規模言語モデルがやっている「次の単語を当てる」学習も、突きつめればこの一点の変奏にすぎません。 次回 は、いちばん小さなモデルであるニューロン1個を取り上げて、そのつまみが実際に何をしているのかを、もう一段だけ細かくのぞいてみます。 連載「手で動かすAI」 次回 → ニューロン1個は何をしているのか

公開: 2026年7月20日 · 更新: 2026年7月21日 · Toshihiko Arai
倍音のスペクトラムから基音の棒だけを抜いても、脳が倍音の間隔から高さを読み取り、同じ高さに聞こえることを示す図

消えたはずの低音が聞こえる ── ミッシング・ファンダメンタル

スマートフォンの小さなスピーカーで音楽を聴いても、ベースラインが今どの高さを弾いているかは、ちゃんと低いまま追えます。よく考えると不思議です。あの数ミリのスピーカーは低い音がとても苦手で、ベースの最低音のあたりは大きく痩せて(減衰して)しか出ません。低音の量はごっそり削られているのに、その音の高さは低いまま届いている。同じことは電話でも起きていて、電話回線はおよそ300Hzから下をばっさり削るのに、低い男性の声は低い高さのままに聞こえます。 このからくりがミッシング・ファンダメンタル(失われた基音)と呼ばれる知覚現象で、鳴らしていない低音が聞こえる差音の回 の終わりに、少しだけ顔を出した話でもあります。今回はこれを主役に据えて、なぜ「無いはずの低音」が聞こえるのか、その仕組みを音デモで確かめられるところまで掘っていきます。 低音は削れても、音の高さは残る まず言葉をそろえておきます。楽器の音のような高さのある音は、いちばん低い成分である基音と、その整数倍の高さの成分である倍音の重なりでできています。ベースが110Hzの音を弾けば、110Hzの基音に、220Hz、330Hz、440Hz……の倍音が乗る。この配合が音色を決める、というのが倍音の回 でやった話でした。 小さいスピーカーは、この一家のうち低いほうがとても苦手です。低い音を空気に伝えるには大きな膜でたくさんの空気を押す必要があり、スマホやノートPCのスピーカーは、数百Hzから下になるほど急に力が落ちます(ある実測の例では、iPhoneのスピーカーが低域で1オクターブあたり十数デシベルも痩せていました。機種や世代で差はあります)。まったく出ないわけではありませんが、110Hzのような最低音は、上の倍音に比べてぐっと小さくしか鳴りません。だからスマホで聴くベースは、実際に低音の“量”がかなり削られています。低音がよく出るイヤホンと、小さなスピーカーとで同じ曲を聴き比べれば、この差はすぐに分かります。 ここで取り違えやすいのですが、削られているのは低音の“量”(エネルギー)であって、音の“高さ”ではありません。低い成分が痩せれば、音はやや軽く・細くなります。これは実際に耳で分かる変化で、基音をしっかり出せる環境ほど、ベースは太くずっしり聞こえます。それでも、その音が指し示す高さ ── ベースが110Hzを弾いている、という高さの感覚 ── のほうは、量が減っても頑固に残る。量は削れても、高さは残る。この不思議の正体を、次に見ていきます。 高さを決めているのは、基音の棒ではない 種明かしの鍵は、音の高さがそもそも何で決まっているかにあります。素朴には「スペクトラム(どの高さの成分がどれだけ含まれるかのグラフ)のいちばん低い棒が、音の高さを決める」と考えたくなりますが、これが違うのです。音の高さの正体は、波が1秒に何回くり返すかでした。110Hzの音とは、波形が1秒に110回、同じ形をくり返す音のことです。 ここで倍音の並びを見直すと、面白いことに気づきます。110Hz、220Hz、330Hz……と等間隔に並んだ成分を足し合わせた波は、1秒に110回くり返します。では、いちばん下の110Hzだけを抜いたら? 残りは220、330、440……ですが、この組み合わせが共通してくり返せる周期は、やはり1秒に110回のまま。330Hzは220Hzの整数倍ではないので、両者の波が同じ顔に戻るのは110分の1秒ごとだからです。つまり基音の棒を抜いても、波形の形は変わるのに、くり返しの間隔だけは頑固に変わらないのです。 そして私たちの聴覚は、この「くり返しの間隔」のほうを音の高さとして聞いています。脳は届いた成分の一本一本を別々の音としてではなく、110Hzおきに整然と並んだひとそろいとして束ね、その並びが指し示す根っこ、つまり基音の高さにラベルを貼る。だから基音の位置が空でも、倍音の間隔さえ残っていれば、聞こえる高さは変わりません。逆に言えば、スピーカーや電話回線がどれだけ低音を削っても、倍音という「基音の痕跡」が届く限り、脳が毎回その場で高さを復元してくれるわけです。ここで復元されるのはあくまで音の高さで、削られた低音の量そのものが戻るわけではない点に注意してください。低音が苦手な機器ほど、この錯覚に助けられていることになります。 触ってみる 理屈はここまでにして、耳で確かめるのがいちばんです。基音110Hzと倍音8本を鳴らしながら、基音をオン・オフできるデモを用意しました。ぜひイヤホンかヘッドホンで聴いてください。低音がよく出る環境ほど、基音のオン・オフでの“量”の変化も、このあとのオクターブ比較も、はっきり分かります。 うまく表示されないときは、デモを別タブで開く 。 鳴らしたまま「基音を抜く」を押すと、スペクトラムの110Hzの棒が消えて、音はすこし軽く・細くなります。低音の“量”が減るからで、これは実際に起きる変化です。ところが、音の“高さ”のほうは動きません。ベースが弾いている高さは110Hzのまま。さらに「2倍音まで抜く」「3倍音まで抜く」と下から順に抜いていっても、残った倍音の間隔が110Hzのままなので、高さは保たれ続けます。量は減るのに、高さは残る。この二つを分けて聴くのがコツです。 ここでいちばん効くのが「比較用:本当のオクターブ上」です。これは倍音の間隔そのものを220Hzに広げた音で(この音自身の基音は220Hzで、こちらは実際に鳴っています)、はっきり1オクターブ高く聞こえます。「基音を抜く」音と並べると、これが決定的な証拠になります。どちらの音にも110Hzの成分は含まれていません。それなのに、倍音の間隔が110Hzの「抜く」ほうは低い110Hzに聞こえ、間隔を220Hzに広げたこちらは高い220Hzに聞こえる。同じ「110Hzがそこに無い」音なのに、聞こえる高さがきっちり1オクターブ違うのです。つまり脳は、低音が実在するかどうかではなく、倍音がどんな間隔で並んでいるかを読んで高さを決めている。ここまで来ると、低音を大きく削るスピーカーや電話でもベースの高さが分かる理由が、すとんと腑に落ちるはずです。 差音とは別の仕組み 「そこに無い低音が聞こえる」と聞いて、差音の回 を思い出した方のために、両者の関係を整理しておきます。あちらで扱った差音は、2つの音が歪みのような非線形を通ったときに、2つの周波数の差にあたる成分が物理的に新しく生まれる現象でした。生まれた低音は空気中や耳の中に実在し、条件がそろえばマイクでも測れます。 今回のミッシング・ファンダメンタルは、物理の側では最後まで何も起きません。デモのスペクトラムが示すとおり、基音の位置はずっと空のままで、どこを測ってもその低音は存在しない。それでも聞こえるのは、倍音の並びから脳が基音を推定しているからです。片方は信号の中に本当に生える低音、もう片方は頭の中でだけ鳴る低音。仕組みはまったく別ですが、どちらも「鳴らしていない低さを耳が聞く」仲間で、パイプオルガンの合成低音のように、2つが同じ高さを指して見分けがつかなくなる場面もあります。 鐘の音は、鳴っていない高さに聞こえる この脳の推定ぶりがいちばん劇的なのは、鐘の音かもしれません。ここまでの話は「倍音がきれいな整数倍に並ぶ」ことが前提でしたが、鐘やベルの部分音(鳴っている成分の一本一本のことで、整数倍に並ぶとは限らないのでこう呼びます)は、整数倍からずれてバラバラに並びます。ところが教会の鐘のゴーンという音を聞いて、私たちはちゃんと「この鐘はこの音程」と感じる。その打音と呼ばれる音程を調べると、実際の部分音がほとんど無い高さであることが珍しくありません。部分音のうち上のほうの数本が、たまたまほぼ2倍・3倍・4倍の関係に並ぶため、脳がその共通の根っこを計算して、そこに音程を聞いてしまうのです。鐘の音程とは、金属の中ではなく聴き手の頭の中で決まっている、と言ってもそう大げさではありません。 ここで宣伝を少しだけ。筆者が個人開発している音感トレーニングアプリ Harmonize には、チューブラーベル(NHKのど自慢の鐘でおなじみの、あの金属管の楽器です)の音色が入っています。この音は録音ではなく、整数倍からわざとずらした部分音を数本重ねる加算合成で作っているのですが、ずれた並びのまま鳴らしても、耳にはちゃんとひとつの音程として届きます。作っている側からすると、最後のまとめ上げを聴き手の脳にやってもらっている感覚です。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play どう実現するか デモの中身は、倍音の回 の加算合成とほとんど同じです。違いはたった一箇所、ループを1からではなく2から始めること。つまり基音を最初から作らないだけです。 // 基音110Hzを作らずに、倍音だけを鳴らす const f0 = 110; for (let n = 2; n <= 8; n++) { // n = 1(基音)を飛ばす const osc = ctx.createOscillator(); osc.frequency.value = f0 * n; // 220, 330, 440, ... Hz const gain = ctx.createGain(); gain.gain.value = 1 / n; osc.connect(gain).connect(master); osc.start(); } このコードはどこにも110Hzを出力していません。スペクトラムを見ても110Hzの棒は立ちません。それでも再生すると110Hzの低さに聞こえる。最後の一手を担当しているのが聴き手の脳なので、プログラム側の仕事はここまでで終わりです。逆に、電話のような「低音を削る装置」をまねたければ、普通に作った音をハイパスフィルタ(低い成分を削るフィルタ)に通すだけで、声の低さが保たれたまま痩せていく様子を確かめられます。 参考文献 青木直史『サウンドプログラミング入門――音響合成の基本とC言語による実装 』技術評論社(2013)。倍音と加算合成を含め、音の基礎をC言語の実装つきで解説する定番書。 現象の全体像は Wikipedia: Missing fundamental 、鐘の打音は Wikipedia: Strike tone が詳しいです。 デモの実装には Web Audio API を使用。倍音1本ごとに OscillatorNode を立て、GainNode で抜き差ししています。 基音の棒を消しても、低さは消えない。ぜひイヤホンで、その瞬間を聴いてみてください。 ...

公開: 2026年7月20日 · Toshihiko Arai
ボコーダーから規則合成・波形接続・ニューラルTTSへ、機械の声が90年かけて自然になっていく年表の図

機械はどうやって喋ってきたか ── ボコーダーからAI音声まで

スマホの読み上げやAIアシスタントの声は、もう人間と聞き分けられないところまで来ています。一方で、少し前の機械の声を覚えている人も多いはずです。SF映画のロボットの声、昔のカーナビのぎこちない案内、駅のホームの継ぎはぎめいたアナウンス。同じ「機械に喋らせる」なのに、この差はどこから来たのでしょうか。 今回はプログラムを書く手をいったん休めて、機械の声の90年をたどる読み物の回です。ただし年表を眺めるだけの話にはしません。ボコーダーからAI音声まで、どの時代のどの方式も、やっていることは実は同じで、声をいったん分解して、組み立て直しています。違うのは、分解したときに何を捨てて、何を残すか。それだけです。そして捨てたものの分だけ声は機械っぽくなり、残せるものが増えるほど自然になる。この一本の物差しを持つと、90年ぶんの歴史が一枚の図に収まります。 以下、この4つを順にたどります。真ん中には、自分の声をその場でロボット声に変えられる音デモを置きました。 ロボット声は電話の発明品だった ── ボコーダー 出発点は意外にも、喋る機械を作ろうという話ではありません。1930年代、ベル研究所の技術者ホーマー・ダドリーが取り組んでいたのは、電話の声をもっと少ない情報量で送れないか、という通信の問題でした。声の波形をそのまま送ると、電話線1本ぶんの帯域をまるまる使ってしまう。もっと圧縮できないか。 ダドリーの答えがボコーダー(vocoder、voice coder の略)です。発想はこうです。声の波形そのものを送るのはやめて、まず声をいくつかの周波数の帯域に分けます。低い帯、中くらいの帯、高い帯。そして帯域ごとに、音量が時間とともにどう動くかという曲線だけを取り出す。この音量の動きの曲線はエンベロープ と呼ばれます。声そのものに比べると、エンベロープはずっとゆっくりした信号なので、はるかに少ない情報量で送れます。受信側では、ブザーのような音源を同じ帯域に分け、送られてきたエンベロープどおりに各帯域の音量つまみを動かして、声を組み立て直す。 つまりボコーダーが「声の本体」とみなしたのは、帯域ごとの音量エンベロープの束です。束が声の聞き取れる中身、いわば口の動きに相当し、波形そのもの、つまり声帯の細かな振動が持っていた「誰の声か」という個性は、思い切って捨てる。フォルマント合成の回 で、声は音源と口の形のフィルタの掛け算だという source-filter モデルを紹介しましたが、ボコーダーはその「フィルタ側の動きだけを抜き出して送る」機械だと言えます。 そして、この「捨てた」ことが思わぬ副産物を生みます。受信側の音源は何でもいいのです。ブザーの代わりにシンセサイザーのノコギリ波を使えば、ノコギリ波が喋る。和音を入れれば、和音が喋る。声の高さの情報は捨てているので、出てくる声の高さは音源側で自由に決められます。人の言葉なのに声帯の生々しさがない、あの独特のロボット声はこうして生まれ、通信装置だったはずのボコーダーは、のちに楽器として音楽の世界で愛されることになりました。 ちなみにベル研究所は1939年のニューヨーク万国博覧会で、この技術から派生した VODER という「鍵盤で操作して喋らせる機械」を実演して見せています。また第二次世界大戦中には、ボコーダーで声を分解してから暗号化する連合国側の秘話通信システム SIGSALY にも使われました。ロボット声は、遊びではなく通信と戦争の真剣な道具として始まったわけです。 1939年ニューヨーク万国博覧会での VODER の実演。オペレーターが鍵盤を操作して機械に喋らせ、来場者が取り囲んだ(Bell System Technical Journal, 1940。パブリックドメイン, via Wikimedia Commons) 秘話通信システム SIGSALY(1943年)。ボコーダーで分解した声を暗号化し、連合国首脳の会話を守った(U.S. Army/NSA。パブリックドメイン, via Wikimedia Commons) 触ってみる 説明より体験のほうが早いので、チャンネルボコーダー(帯域=チャンネルに分けるボコーダー)のデモを用意しました。キャリア(着せ替え先の音源)はノコギリ波です。まずはそのまま「ロボット声にする」を押すと、内蔵の合成音声が「あーいーうー」と口を動かし、その口の動きがノコギリ波に乗り移ります。下の棒グラフが、いままさに送られている「帯域エンベロープの束」です。「あ」「い」「う」で束の形が変わるのを見てください。 マイクボタンを押してブラウザにマイクを許可すると、自分の声がそのままロボット声になります(https のページでだけ動きます。イヤホン推奨)。何か喋りながら「ロボット声の高さ」を動かすと、喋る内容はそのままで声の高さだけが変わります。高さの情報は捨てられていて、キャリア側が握っている証拠です。 うまく表示されないときは、デモを別タブで開く 。 録音ゼロで喋らせる ── 規則合成 ボコーダーには決定的な限界があります。入力に「いま人が喋っている声」が要ることです。声の衣装を着せ替えることはできても、文字を渡して勝手に喋ってもらうことはできません。テキスト読み上げ(TTS: text-to-speech)を作るには、人の声を借りずに、声をゼロから組み立てる必要があります。 その道具立てはすでにこの連載に登場しています。フォルマント合成 です。ノコギリ波をバンドパスフィルタに通し、口の共鳴の山(フォルマント)の位置 F1・F2 を選べば母音が決まるのでした。ならば「こ・ん・に・ち・は」という音の並びに合わせて、フォルマントの位置を時間どおりに動かす規則を書けば、機械は文字から喋れるはずです。これが規則合成と呼ばれる方式で、1980年代に実用の水準へ引き上げたのが、MITのデニス・クラットのフォルマント合成器でした。この系統の合成器は DECtalk などとして製品化され、車いすの物理学者スティーヴン・ホーキングが生涯使い続けた「声」も、クラットの合成器に由来する系統です。 規則合成の声は、身近なところにいまも現役でいます。動画の「ゆっくり実況」でおなじみのあの声は、アクエスト社の規則音声合成エンジン AquesTalk によるもので、録音した人の声を使わずに規則で組み立てる、この系統の音です。 一度でも聴けば分かるとおり、規則合成の声は明瞭ですが、はっきり機械の声です。理由は物差しに当てるとすぐ見えます。この方式は人の録音をいっさい残していません。残したのは「声とはこう動くものだ」という設計図、つまり人間が書き下せた規則だけです。実際の声が持つ揺らぎや癖のうち、規則にできなかったものはすべて捨てられる。とりわけ子音 は数十ミリ秒の時間設計のかたまりで、規則で書き切るのが難しく、機械っぽさの大きな源になります。その代わり、録音が要らないので驚くほど軽く、辞書にある言葉なら何でも、何時間でも喋れる。「ゆっくり」の声が四十年前の方式のまま愛され続けているのは、この軽さと、むしろ味になった機械っぽさのおかげでしょう。 ところで宣伝を少しだけ。筆者が個人開発している音感トレーニングアプリ Harmonize でも、「あー」と歌う声の音色をフォルマント合成で試作しています。まだ公開していない開発中の音色ですが、中身はまさにこの系統で、歌うだけで喋りはしません。規則合成のいちばん端っこの親戚と言えます。 ...

公開: 2026年7月19日 · Toshihiko Arai

SQLiteのボトルネックはディスクの遅さではない:ロックとfsyncをPythonで実測する

「ファイルベースだから遅い」は本当か SQLite をサーバー用途に使う話をすると、たいてい「やめておいたほうがいい」と言われます。これ自体はでたらめな脅しではなく、SQLite の公式ドキュメント(Appropriate Uses For SQLite )自身が、同時書き込みの多いサービスにはクライアント/サーバー型のデータベースを勧めています。では、なぜ駄目なのか。理由を考え始めたとき、まず思い浮かぶのはこんな筋書きではないでしょうか。SQLite の実体はただのファイルで、ファイルの読み書きはディスク I/O。Redis のようなキャッシュ(メモリ上でキーと値を預かる、キャッシュ用途の定番サーバーです)が「メモリで持つから速い」と説明されるのなら、その裏返しで、ディスクに読み書きする SQLite は遅いはずだ——一見、筋が通って聞こえます。 本当にそうか、まず手元で測ってみます。10 万行のテーブルに主キーで 1 件ずつ問い合わせる、キャッシュ用途を想定したいちばん素朴な読み取りです。コードは Python の標準ライブラリだけで動きます。実験はすべて使い捨ての Docker コンテナ(Linux)上で実行していて、再現手順は記事末尾にまとめました。 import sqlite3, time, random con = sqlite3.connect("cache.db") con.execute("CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)") with con: con.executemany("INSERT INTO kv VALUES (?, ?)", ((f"k{i}", "x" * 100) for i in range(100_000))) keys = [f"k{random.randrange(100_000)}" for _ in range(200_000)] cur = con.cursor() t0 = time.perf_counter() for k in keys: cur.execute("SELECT v FROM kv WHERE k = ?", (k,)) cur.fetchone() print(200_000 / (time.perf_counter() - t0)) 手元(Apple シリコンの Mac 上の Linux コンテナ、SSD)ではこうなりました。 ...

公開: 2026年7月18日 · Toshihiko Arai
閉鎖・バースト・遷移という3段のタイムラインで破裂音ができている図

「た」は音ではなく出来事 ── 子音の正体(後編・破裂音)

前編 では、「さ」の子音 s を白色ノイズの成形で作りました。摩擦音は音源こそノイズですが、「すーっ」と伸ばせるぶんだけ、まだ母音の親戚のような扱いができた——フィルタを設定して鳴らしっぱなしにすればよかったわけです。 後編の相手はそうはいきません。「た」「か」「ぱ」。試しに「た」の子音だけを伸ばそうとしてみてください。「たーーー」と言っても伸びているのは後ろの「あ」で、t 自体はどうやっても伸ばせません。伸ばせる状態がそもそも存在しないからです。この仲間は破裂音と呼ばれ、口の中を一度完全にふさぎ、ためた息を一気に開放して作ります。その中身は、鳴りっぱなしの音ではなく、数十ミリ秒のあいだに決まった順番で起きる一連の出来事です。 「た」を解剖する 「た」と言った瞬間の音を、時間×周波数の平面(スペクトログラム)で模式的に描くとこうなります。 まず①閉鎖。舌先を歯ぐきに押し当てて口をふさぎ、息の圧力をためます。外に出る音はゼロ、およそ0.1秒弱の完全な無音です。面白いのは、この「音がないこと」自体が子音の部品だということです。無音の谷があるからこそ、次の破裂が破裂として聞こえます。実際、日本語はこの無音の長さを聞き分けていて、「あた」の閉鎖を長く引き伸ばすと「あった」になります。促音「っ」の正体は、長めの無音なのです。 次に②破裂。ふさぎを開放した瞬間、たまった圧が一気に抜けて、「プツッ」という10ミリ秒ほどの短いノイズが弾けます。バーストと呼ばれる成分で、破裂音の中で唯一「音が鳴っている」部品ですが、あまりに短いので単体で聴いてもクリック音にしか聞こえません。 最後に③遷移。破裂の直後、口は子音の構えから次の母音「あ」の構えへ動いていきます。口の形が動けば、フォルマント (口の共鳴で強調される周波数の山)も動く。つまりフォルマントが、子音の構えの位置から母音の定常位置へ、50ミリ秒ほどの坂を滑り降りていきます。このフォルマント遷移が終わると、ようやく④母音の定常状態に着地します。 ①無音、②バースト、③坂、④定常。「た」の子音部分とは①〜③の合計およそ0.14秒の段取りのことで、どこにも保てる状態がありません。前編の言い方を借りれば、摩擦音がまだ「状態」の顔も持っていたのに対し、破裂音は純粋な「出来事」です。作る側の仕事も、フィルタの設定から時間の設計へと完全に切り替わります。 坂の入り方だけで「だ」が「が」になる では「た」と「か」と「ぱ」は何が違うのでしょうか。ふさぐ場所です。「ぱ」は唇で、「た」は舌先で、「か」は舌の奥で口をふさぎます。ふさぐ場所(調音位置と呼びます)が違うと、②バーストの音色と、③遷移の坂の出発点が変わります。ぱ行の破裂は低くこもった「ポッ」、た行は高く鋭い「ツッ」、か行は中域に固まった「コッ」。そして破裂した瞬間の口の形が違うのですから、そこから母音へ向かうフォルマントの坂も、それぞれ別の場所から始まります。 この「坂の出発点」が、実は子音の聞き分けの決定打になっています。それを一番きれいに見せてくれるのが、有声の仲間「ば・だ・が」です。 どちらのパネルも、バーストは同じ、着地する母音も同じ「あ」。違うのは2番目のフォルマント F2 の坂の入り方だけです。低いところから駆け上がってくれば「ば」、少し上からすっと降りてくれば「だ」、もっと高いところから深く落ちてくれば「が」。音として鳴っている部品はほとんど共通なのに、ほんの50ミリ秒の坂の形だけで、耳は「唇でふさいだな」「舌先だな」「奥だな」と聞き分けてしまいます。これは1950年代に音声合成の研究者たちが人工的な音で確かめた古典的な発見で、子音の情報が「音そのもの」ではなく「母音への入り方」に載っていることを示しています。 触ってみる その時間の設計を、スライダー3本で組み立てるデモです。「た」「か」「ぱ」のボタンを押すと、閉鎖→バースト→遷移→母音のタイムラインを Web Audio が実行します。閉鎖の長さを伸ばすと「あた」が「あった」になり、バーストを0%にすると弾けが消え、遷移を120msまで引き伸ばすと「わ」や「や」のような滑らかな音に化けていく——3つの部品がそれぞれ何を担っているかを、壊しながら確かめられます。下の実験パネルでは、F2 の出発点のスライダーを動かすだけで「ば→だ→が」が入れ替わる境目を探せます。 うまく表示されないときは、デモを別タブで開く 。 聴いてみると分かるとおり、出てくるのは「たしかに、たっぽい」という音であって、人の「た」そのものではありません。実はこれこそが破裂音の性格です。閉鎖の長さが数十ミリ秒ずれる、バーストの帯域が少し違う、坂の形がわずかに崩れる——そのどれもが聞こえを変えてしまうほど、耳は破裂音の時間構造に敏感で、パラメータをきっちり合わせても「それっぽい」の先へはなかなか行けません。母音なら3本のフィルタで済んだのに、子音では研究者たちが何十年もかけて規則を磨いてきた理由が、スライダーを触っているだけでも実感できると思います。 どう実現するか 作る側から見ると、破裂音の実装とは「無音を含めたスケジュール表を書くこと」です。Web Audio API には、パラメータの未来の値を予約する仕組み(setValueAtTime と各種 Ramp)があるので、タイムラインをそのまま書き下せます。 const t0 = ac.currentTime; const tBurst = t0 + 0.07; // ① 閉鎖 = 0.07秒なにも置かない // ② バースト:短いノイズを帯域で着色(た=4200Hz / か=1700Hz / ぱ=700Hz) burstGain.gain.setValueAtTime(0.5, tBurst); burstGain.gain.exponentialRampToValueAtTime(0.001, tBurst + 0.012); // ③ 遷移:フォルマントのフィルタを出発点に置き、母音へ滑らせる const tv = tBurst + 0.03; // 声帯の始動 bpf2.frequency.setValueAtTime(1800, tv); // F2 の出発点(た の構え) bpf2.frequency.linearRampToValueAtTime(1250, tv + 0.05); // 「あ」へ50msの坂 vowelGain.gain.setValueAtTime(0, tv); vowelGain.gain.linearRampToValueAtTime(0.9, tv + 0.02); // ④ あとは母音の定常 C風に書くなら、サンプル番号がどの区間にいるかで振る舞いを切り替えるだけの、行儀のよい状態機械になります。 ...

公開: 2026年7月18日 · Toshihiko Arai
濃紺の背景に、WEB MIDIの大きなタイポグラフィ、ピアノロールを表示したブラウザウィンドウ、そこへ差し込まれるMIDIケーブルと鍵盤を描いたレコードジャケット風アイキャッチ

ブラウザが楽器とつながる ── Web MIDIとコンピューターでの扱い

前回の番外編MIDI編① で、MIDIは音の波形ではなく「いつ・どの高さを・どの強さで弾け」という数バイトの指示を送る規格だ、という話をしました。ただ、指示は受け取り手がいて初めて意味を持ちます。届いた3バイトを解釈して、鳴らして、覚えて、並べ替える。その受け取り手の主役が、コンピューターです。 そして今は、専用ソフトを買ってこなくても、この受け取り手を自分で書けます。ブラウザのJavaScriptには、USBでつないだMIDIキーボードと直接会話するためのWeb MIDI APIがあり、届いたバイト列は前回デモで眺めたものとまったく同じ形をしています。MIDIは特別な機材の中だけの話ではなく、コードから触れる身近な信号なのです。 コンピューターが指示に対してできることは、突き詰めるとこの図の3つです。鳴らす、記録する、作り変える。今回はこの3つを、ブラウザの上で実際に組み立てていきます。 ブラウザが楽器とつながるまで Web MIDI APIの流れは、カメラやマイクを使うWebアプリとよく似ています。ページがAPIを呼ぶと、ブラウザがユーザーに「このサイトにMIDIデバイスの使用を許可しますか?」と確認し、許可されて初めてポートの一覧が手に入ります。 入り口はnavigator.requestMIDIAccess()のひとつだけ。返ってくるMIDIAccessオブジェクトの中に、入力ポート(楽器からブラウザへ)と出力ポート(ブラウザから楽器へ)が並んでいます。入力ポートのonmidimessageに関数を登録すれば、鍵盤を弾くたびにその関数へバイト列が届きます。 const access = await navigator.requestMIDIAccess(); for (const input of access.inputs.values()) { input.onmidimessage = (ev) => { const [status, note, velocity] = ev.data; // 例: [0x90, 0x3C, 0x64] if ((status & 0xF0) === 0x90 && velocity > 0) { playNote(note, velocity); // あとは前回の知識で読める } }; } ev.dataの中身は、前回解剖した3バイトそのものです。ステータスの上位4ビットで命令を見分け、ノート番号とベロシティを取り出す。規格が40年変わっていないおかげで、1980年代のシンセをUSB-MIDIインターフェイス経由でつないでも、この数行がそのまま動きます。 対応状況は正直に書いておきます。ChromeとEdge、Firefox(バージョン108以降)では使えますが、SafariはmacOSでもiPhoneでも対応していません。AppleのWebKitチームが、MIDI機器の固有IDがユーザーの追跡(フィンガープリント)に使われることを懸念して実装を見送り続けているためです。接続が許可制なのも同じ理由です。スマートフォンでこの記事を読んでいる方が実機接続を試せないのは心苦しいところですが、この後のデモは実機なしでも最後まで動くように作ってあります。 時刻を付ければ、それはもうシーケンサー 届いたバイト列を鳴らすだけなら、コンピューターは高級なMIDIケーブルにすぎません。面白いのはここからで、届いたイベントに受け取った時刻を添えて配列に積んでいくと、それだけで演奏の記録になります。 const events = []; function record(bytes) { events.push({ t: (performance.now() - recStart) / 1000, bytes }); } 再生は逆再生ならぬ逆過程で、時計を走らせて、時刻が来たイベントから順に「鳴らす」処理へ流し込むだけです。これがシーケンサーの最小形で、DAWのピアノロールで音符をつまんで動かす操作は、この配列のtやノート番号を書き換えているにすぎません。 録音した音声データと決定的に違うのは、時間と音程が別々の数値として持たれていることです。Tape Stopの回 で、テープやサンプルは再生速度を変えると音程まで一緒に変わる、という物理を確かめました。指示の列にはそれがありません。 波形の2倍速は振動そのものの2倍速なので、音程が1オクターブ上がります。指示の列の2倍速は、イベントの間隔を半分にするだけ。各イベントが運ぶノート番号は手つかずなので、音程はそのままです。テンポと音程を独立に扱えるこの性質こそ、打ち込みという制作スタイルの土台になっています。 表情のコントロールも同じ言葉で書けます。ピッチベンドはE0で始まる3バイト(値だけ14ビットに拡張されています)、ビブラートなどに使うモジュレーションホイールはB0 01 xxというコントロールチェンジ。鍵盤のオンオフと同じただのメッセージなので、シーケンサーは区別なく記録・再生できます。前回「指示の缶詰」として紹介したSMF(.midファイル)は、まさにこの時刻付きイベント列をファイルに書き出したものです。 弾いて、記録して、倍速で聴く ここまでの部品を全部つないだデモです。USBのMIDIキーボードがあれば「MIDI機器に接続する」から実機で、なければ画面の鍵盤で、同じように遊べます。 うまく表示されないときは、デモを別タブで開く 。 ...

公開: 2026年7月18日 · Toshihiko Arai