平均律の和音はわずかなズレで音量が脈打ち(うなり)、純正律では倍音がぴたり重なって脈打ちが止まる様子を示す波形の図

きれいな和音はなぜ「うなり」が消えるのか ── 平均律と純正律

よく調律された楽器で和音を鳴らしたとき、澄みきって聞こえることもあれば、かすかに「ワウワウ」と音量が揺れて聞こえることもあります。この揺れは気のせいではなく、周波数から計算で求まる、はっきりした物理現象です。今回は、その揺れ——うなり——の正体をたどり、平均律と純正律という二つの調律を、プログラムでどう鳴らし分けるのかを見ていきます。 少しだけこの連載のことを。ここでは楽器の音をプログラムで作る仕組みを紹介しています。筆者は個人で音感トレーニングアプリ Harmonize を作っていて、これはまさに今回の「うなりが消える位置」を耳で探し当てる練習をするアプリです。純正律や平均律が音楽やトレーニングのなかでどんな意味を持つかは Harmonize の Q&A にまとめてあるので、本記事では現象そのものと、その作り方に絞ります。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play うなりには二段階ある まず揺れそのものから。じつは、うなりの起き方には二段階あって、ここを分けておかないと話が混ざります。 ひとつめは、高さのごく近い二つの音のあいだで起きるうなりです。440Hz と 442Hz のように基音(その音のいちばん低い成分)どうしが近いと、二つの波が少しずつズレていき、山と山が重なる瞬間は大きく、山と谷がぶつかる瞬間は小さくなる。これがくり返されて音量が脈打ちます。その速さは二つの周波数の差にちょうど等しく、$442 - 440 = 2$ で1秒に2回。差が縮むほどゆっくりになり、ぴったり同じ高さで消えます。ここで大事なのは、これは倍音がなくても、純粋なサイン波どうしでも起きるということです。 問題はふたつめ、ドとソのように離れた二つの音——和音——のうなりです。ドが 261Hz、ソが 392Hz なら、基音どうしは 130Hz も離れていて、これは「ゆっくりした脈打ち」ではなく、もう別の一つの高さとして聞こえてしまいます。だから基音だけを見るかぎり、和音にうなりは出ません。実際、サイン波でこの5度を鳴らすと、少し外れて感じることはあっても、ワウワウとは揺れないのです。 それでも本物の楽器では、和音にはっきりうなりが出ます。その正体が倍音です。 澄む条件は「倍音がぴたり重なる」 ひとつの楽器の音には、基音のほかに、その2倍・3倍・4倍…の高さの成分がいくつも含まれています。これが倍音です。だから和音を鳴らすと、耳もとには両方の倍音がずらりと並びます。そして、片方の高い倍音と、もう片方の低い倍音が「近いけれど少し違う高さ」で出会うと、そこで——さきほどの近い二音のうなりとまったく同じ理屈で——うなりが生まれます。 上の図は、その重なるはずの二つ——ドの3倍音と、ソの2倍音——を波として重ね、足し合わせたものです。純正律では二つが同じ高さでずっとそろっているので、足しても音量は変わりません。平均律ではソをわずかに低く取るぶん、二つの波が少しずつずれていき、山と山が重なって大きくなる瞬間と、山と谷がぶつかって打ち消し合う瞬間をくり返します。この音量の脈打ちが、うなりの正体です。脈打ちの速さ(毎秒およそ0.9回)は、二つの波の周波数の差にちょうど等しくなります。和音が澄むか濁るかは、この「重なるはずの倍音」がぴたり合うかどうかで決まるわけです。だから、倍音をあまり持たない音(サイン波に近い音)ほど和音のうなりは出にくく、倍音の豊かな楽器ほどはっきり出る。楽器によって濁り方が違うのは、このためです。 では、重なるはずの倍音がぴたり合うのはどんなときか。二つの音の周波数が簡単な整数比のときです。ソをドのちょうど $3/2$ 倍にすると、ドの3倍音($3 \times$ 基音)とソの2倍音($2 \times \tfrac{3}{2} \times$ 基音 $= 3 \times$ 基音)が同じ高さになり、うなりが消えます。長3度なら $5/4$、オクターブなら $2/1$。こうして音程を単純な整数比でそろえる決め方が純正律で、うなりが最も少ない、いちばん澄んだ響きです。 一方、ピアノなどが使う平均律は、1オクターブ(周波数がちょうど2倍になる幅)を12の等しい比で割った実用的な妥協です。半音ひとつが $2^{1/12}$(およそ 1.0595)倍で、どの調へ移っても均一に弾ける代わりに、どの音程もわずかに整数比からズレます。平均律の5度は $2^{7/12} \approx 1.4983$ で純正の 1.5 にごく近く、うなりはゆっくり。3度は $2^{4/12} \approx 1.2599$ で純正の 1.25 より上ずっていて、はっきりしたうなりになります。純正律と平均律のこうした使い分けの事情は Q&A のほうに譲ります。 触ってみる 鳴らして聴くのが早いので、デモを用意しました。長3度や完全5度、ドミソの和音を鳴らしたまま、平均律と純正律を切り替えてみてください。平均律ではうなりが出て、純正律にした瞬間に止まって澄む——その違いが、下のスコープの音量の脈打ちとあわせて分かります。 うまく表示されないときは、デモを別タブで開く 。 ...

公開: 2026年7月4日 · Toshihiko Arai
ノイズを詰めた輪(リングバッファ)を一周ごとに平均すると、ギザギザの波がなめらかな波に整いながら減衰し、はじいた弦の音になる図

ノイズをひと吹き、輪に閉じ込めて回すだけで弦が鳴る ── カープラス・ストロング

プログラムで楽器の音を作ろうとすると、道はふたつ思い浮かびます。ひとつは本物をマイクで録って貼りつけるサンプリング。もうひとつは、エンベロープの回 でやったように、波形をいちから組み立てていくやり方です。どちらも「音の形」を外側から与える発想です。 ところが、これとはまるで違う第三の道があります。音の形をこちらから与えるのではなく、弦そのものの振る舞いをプログラムの中で回して、鳴るにまかせる。材料はあきれるほど少なくて、ほんのひと吹きのノイズだけ。それを輪に閉じ込めてぐるぐる回すと、はじいた弦の音が、ちゃんと減衰しながら鳴ります。カープラス・ストロングと呼ばれる、1983年に発表された古くて賢い方法です。 本題の前に、この連載について少しだけ。ここでは楽器の音をプログラムで作る仕組みを、少しずつ紹介しています。筆者は個人で音感トレーニングアプリ Harmonize を作っていて、その中で鳴る楽器の音は、録音の再生ではなく、鳴らすたびにプログラムが計算した合成音です。こうした音づくりの基礎は独学で、古いC言語のサウンドプログラミングの教科書から学びました(参考文献に挙げています)。今回のカープラス・ストロングも、その延長にある小さな一手です。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play 弦をはじくと、何が起きているか 仕組みに入る前に、本物の弦を思い出してみます。ギターでも琴でも、弦をはじくというのは、弦を指でつまんで、でたらめな形にひしゃげさせてから、ぱっと離すことです。離された弦は、そのひしゃげをきっかけに行ったり来たり揺れはじめ、揺れは空気や熱に少しずつエネルギーを奪われて、やがて止まって静かになります。 ここで起きていることは、煎じ詰めるとふたつだけです。ひとつは、最初に一度きりの、でたらめな衝撃が与えられること。もうひとつは、その揺れが時間とともに薄れていくこと。カープラス・ストロングがまねるのは、まさにこのふたつです。でたらめな衝撃はノイズで、薄れていく揺れは、輪の上での平均が受け持ちます。 ノイズを輪に閉じ込めて回す 先に前提をひとつ確かめておきます。コンピューターが音を出すとき、スピーカーへは1秒あたり44100個の数が送られていて、その一つ一つが「その瞬間に膜をどれだけ押し出すか」を表しています。音の正体は、つまるところこの数の列です。サイン波もピアノの音も、毎秒4万個あまりの数の並びにすぎません。ここが分かっていると、これから話す仕組みが素直に飲み込めます。 カープラス・ストロングで用意するのは、その数を何十個か並べた短い列です。ただし端まで来たら先頭へ戻る、輪のようにつながった列を使います。データ構造としてはリングバッファ と呼ばれるもので、位置を指す針がぐるぐる回り、端に来たら先頭へ折り返します。 最初に、この輪の全部のマスをノイズ、つまりでたらめな数で満たします。これが弦をはじいた瞬間のひしゃげにあたります。あとは針を順に進めながら、いま指しているマスの数をそのまま音の1サンプルとして出力し、端まで行ったら先頭に戻ってまた出力していく。ここが肝心なのですが、鳴っているのはこの輪に入っている数そのものです。どこかに別のサイン波があって、それを掛けたり変調したりしているのではありません。輪から順に取り出した数の列が、そっくりそのまま音になります。 では音の高さはどこで決まるのか。輪は端まで行くと先頭へ戻るので、出てくる数の列は、輪の長さぶんの間隔で同じパターンをくり返します。数は1秒に44100個の速さで出ていくので、輪のマスが $L$ 個あれば、1秒あたり $44100 \div L$ 回だけそのパターンがくり返される。このくり返しの回数が、そのまま音の高さ(周波数)です。輪が長ければゆっくりくり返すので低い音、短ければ速くくり返すので高い音。出したい高さ $f_0$ が決まれば、輪の長さを $L = f_s / f_0$ マスにすればよい、というだけの話です。ドとラの違いは、輪を何マスにするか、それだけで決まります。 ただし、もし輪の中身を固定したまま回し続けたら、そのノイズが延々くり返す、ざらついたブザーのような音が同じ高さで鳴りっぱなしになるだけです。カープラス・ストロングが弦の音になるのは、針が通り過ぎるたびにマスの中身を書き換えるからです。数を出力すると同時に、そのマスを「自分と隣の平均」にして、ほんの少し小さくして書き戻す。すると輪の形は、一周ごとに少しずつ変わっていきます。 なぜ平均するだけでノイズが澄んだ音になるのか。隣どうしを平均するという操作は、値が細かく上下している部分ほど強くならします。細かい上下は高い音の成分ですから、平均を通すたびに高い成分が削られていく。これはローパスフィルタ、つまり高い音を通しにくくするフィルタそのものです。はじめのノイズには高い成分から低い成分までひととおり入っていますが、一周ごとに高いほうから消えていき、最後に生き残るのは輪の長さぶんの周期でくり返す成分だけ。それが澄んだ音の高さとして聞こえます。 そして高い成分が先に、低い成分があとに消えるということは、音がはじめは明るく、すぐ丸くなって、やがて消えていくということです。この「高い倍音から先に消える」という振る舞いは、本物の弦や、あとで触れるピアノの音の大事な特徴なのですが、カープラス・ストロングではそれを一つ一つ設計する必要がありません。輪の上で平均するという一手の、いわばおまけとして手に入ってしまいます。 触ってみる 言葉で追うより、鳴らして見たほうが早いので、デモを用意しました。上のドレミを押すと、その音程ぶんの長さの輪にノイズを詰めて鳴らします。真ん中の輪の絵は、いま輪の中に入っている値をそのまま描いたものです。押した直後はギザギザのノイズ、赤い針が一周するたびに角が取れて、波がなめらかな形に整いながら少しずつ縮んで消えていく ── さきほどの三段の図が、リアルタイムで動くのを見られます。 うまく表示されないときは、デモを別タブで開く 。 いちばん下のスペクトログラムを見ると、高いほうの成分から順に暗くなっていくのがわかります。これがさっきの「高い倍音から先に消える」です。それから、明るさのつまみを右いっぱいに回してみてください。一周ごとの平均をほとんどやめてしまうと、音は弦ではなく、金属的な反響のようになります。輪を平均する一手こそが、弦らしさの正体だったわけです。 このとき輪は、平均という味つけを失って、ただの遅延ライン(信号を一定時間ためて送り返す仕掛け)にフィードバックがかかっただけの状態に戻ります。これはギターアンプに載っているスプリングリバーブ ── バネに信号を通し、伝わって返ってくる音を拾う装置 ── とそっくり同じ構造です。あの金属的な「ボヨーン」が顔を出すのは、そのためです。 この素のままの音は、ギターともピアノとも少し違う、板に弦を張っただけのような、どこか金属質な響きに聞こえたかもしれません。実際この音は、昔から琴やハープシコードにたとえられてきました。共鳴する箱(ボディ)をまだ一切持っていない、弦の生の振動だからです。 そこで効いてくるのが、はじきの柔らかさのつまみです。真っ白なノイズをそのまま詰めると、はじいた瞬間に高い成分がびっしり入るので、硬いピックで弾いたような金属的なアタックになります。詰める前にノイズを少しならして角を丸めておくと、やわらかい所を指の腹ではじいたような音になり、金属質が抜けてナイロン弦のような丸みが出てきます。輪を回す仕組みはまったく同じまま、はじき方を変えるだけで、琴とナイロン弦のあいだを行き来できるのです。 どう作るか 仕組みが単純なだけあって、中身もほんの十数行です。輪をノイズで満たし、針を回しながら「出力して、隣と平均して書き戻す」を繰り返す。それだけです。 /* カープラス・ストロング(C風の擬似コード) */ int L = round(fs / f0); /* 輪の長さ=音程を決める */ float buf[L]; for (i = 0; i < L; i++) buf[i] = noise(); /* ノイズをひと吹き詰める */ int i = 0; for (n = 0; n < length; n++) { output[n] = buf[i]; /* いまの値を出力 */ int j = (i + 1) % L; /* 隣(輪なので端は先頭へ戻る) */ buf[i] = decay * 0.5f * (buf[i] + buf[j]); /* 隣と平均し、少し縮めて書き戻す */ i = j; /* 針をひとつ進める */ } 数式で書けば、いま鳴っている値 $y[n]$ は、輪の長さ $L$ だけ前の値とそのひとつ前の平均、 ...

公開: 2026年7月4日 · Toshihiko Arai
みるみる減っていく利用量ゲージと、それを見て頭を抱える人を描いたアイキャッチ画像

【続報】フェイブル5、丸1日でゲージの8割が溶けた

昨日、「フェイブル5が復活したので布団の中で200ドルプランの解約を相談した話」 という記事を書きました。従量課金になったら桁が合わない、まずは消費ペースを観察しよう、という話でしたね。 で、観察した結果が出ました。早すぎる結果が。 実測値:1日でこうなった フェイブル5が使えるようになったのが7月2日。翌3日の朝8時時点で、私のゲージはこうなっていました。 フェイブル5:79%消費 Opus 4.8:50%消費(昨日リセットされたばかりのはず) 丸1日で8割です。正直に言うと、昨日は「7日までの食べ放題だ」とばかりに、今のうちに頼めることをフェイブル5に頼みまくりました。だから普段より使ったのは確かです。とはいえ、私という人間が1日にこなせる作業量には限りがあるわけで、日常とそこまで大きくは変わらない。それでこの減り方なんだから、フェイブル5はバカ食いする、というイメージは持ちました。 Opusのほうも異常な速さで減っていて、最初は「リセットおかしくない?」と思ったんだけど、これはたぶんカラクリがあります。後述。 使用感のアップデート 昨日は「劇的に良くなった感じはしない」と書きました。1日使い込んだ今の感想は、少しだけ上方修正です。 作業内容としては、だいぶスムーズに動いてくれている感じはします。Opus 4.8よりは、やりとりが少しだけ円滑になった。指示の意図の汲み取りで引っかかる回数が減った、という感覚ですね。 ただ、プログラミングの精度に関しては正直分からない。コードは見るときは見るけれど、その精度までは確認していないし、やりたいことの要望は今まで通り叶っている。今までだって問題なかったので。要望が普通に叶う段階まで環境ができていると、モデルの精度差って体感に出てこないのかもしれない。どうなんでしょうね。 ちなみにAIに叶えてもらっている「やりたいこと」の中身はというと、たとえばこの「タイピングの神様」。フリック入力や文字入力を練習できるアプリです。15年ほど前に作ったんですが、その後は開発のモチベーションが上がらず、長いことレガシーなまま放置していました。それが最近、AIと組むことで蘇りまして、いまは新しいアイデアをあれこれ盛り込みながら手を入れています。 タイピングの神様 フリック入力や文字入力を練習できるタイピング練習アプリ 今すぐ無料でダウンロード App StoreGoogle Play 「Opusまで減ってる」のカラクリ リセット直後のはずのOpus 4.8が1日で50%。最初は不具合を疑ったんですが、仕組みを調べると納得でした。 7月7日までのプロモーション期間中、フェイブル5は週間利用上限の最大50%まで使える。ここがポイントで、これは別枠のボーナスではなく、普段の週間枠の「内数」なんですね。つまりフェイブル5を使えば使うほど、共通の週間ゲージも一緒に削れていく。 フェイブル5はOpusの2倍レートで枠を食う計算になるので、フェイブルに1日振っただけで週間枠が半分吹き飛んだ、と考えるとゲージの減り方と辻褄が合います。異常でも不具合でもなく、仕様でした。 図にするとこういうことです。 タンクは週間枠の1つだけで、ゲージが2本あるように見えるのは分母が違うから。私の場合、フェイブル5で週間枠の約40%を溶かし、Opusでの通常作業が約10%。合計50%が「Opusゲージの50%」で、同じフェイブル分を50%の専用枠を分母にして見ると「フェイブルゲージの79%」になる。2つの数字は、同じ消費の別表示だったわけです。 もし従量課金だったら幾らだったのか 怖いのはここからです。7月8日以降、フェイブル5はこの週間枠から外れて、クレジット(API価格そのまま:入力$10/出力$50 per 100万トークン)でしか使えなくなります。 サブスクからは正確なトークン数が見えないので概算ですが、Max 20xの週間枠まるごとのAPI換算価値は数百ドル規模と言われています。その半分を1日で、しかも単価2倍のフェイブル5で溶かしたとなると、今日と同じ1日を従量課金でやったら数十ドル〜百ドル級は覚悟する必要がありそう。昨日の記事で「海外のヘビーユーザーが1日110ドル溶かした」という報告に触れましたが、あれは他人事じゃなかったですね。 このペースを1か月続けたらどうなるか、サブスクと並べてみるとこうなります。 月200ドルのサブスクなら横一直線のところが、従量課金だと開始2〜4日でその1か月分を突破して、あとは青天井。「桁が合わない」と言われた意味を、グラフにして改めて実感しました。 で、どうするか 昨日の記事では「クレジットを50ドルだけ入れて減り方を観察する」と書きました。が、1日でこの結果を見せられると、正直それすら微妙になってきています。 だって、今日の消費ペースから逆算すると、50ドルなんて下手したら1日分ですよ。1日で7,000円以上。 誤解のないように言っておくと、この値段が高すぎると言いたいわけではないんです。AIは人の何十倍、何百倍と働いてくれるわけで、その成果がそのまま売上につながる人にとっては、1日7,000円なんてむしろ安い投資でしょう。喜んで払う人がいるのも分かる。ただ私の場合、AIで直接儲けているわけではないんですよね。AIが1000倍働いてくれたからといって、私の収益が1000倍になるわけではない。だからこの出費は、投資ではなく純粋な負担としてのしかかってくる。そこの違いなんです。 なので現時点の気持ちとしては、7月8日以降はいったんOpus 4.8中心に戻ることになりそうです。せっかく最強モデルが帰ってきたのに、食べ放題期間が終わったらしばらくお預け。ちょっと寂しくはありますが、提供条件はこの1か月だけでも何度も変わっているので、また気軽に使える日が来るまでの一時的な距離の取り方かなと思っています。 Codexのことを思い出した で、ここで思い出したんですが、私、Codexにも200ドルプランを払ってるんですよね。 以前は100ドルプランだったんだけど、レビューが追いつかなくなって200ドルに上げた。ところが今度は使い切れずに余っている状態。だったらフェイブル5にクレジットを積むより、余っているこっちを活用する方向で進めたほうがいいのかもしれない。 それに今回の一件で思ったのは、Claude Codeにいつまでも頼りきりなのも、それはそれでリスクだなということ。モデルが突然止まったり、課金体系が変わったりするのを目の当たりにしたわけだから、2本立てで回せる体制にしておくのは保険として正しい気がしています。 それにしても月6万円 ClaudeとCodexで200ドル+200ドル、月400ドル。日本円にして約6万円です。「AIに月6万円も払って、私は一体何をしているんだ」と思う瞬間もなくはない。 ただこれ、感覚としては一番近いのはゲーム課金なんですよね。今、私はAIにハマっている。楽しいから払っている。それだけの話で。 しかもこの熱、いつか絶対飽きると思うんですよ。だから今だけ、今だけね、という言い訳めいた後ろめたさ込みでの6万円です。ソシャゲに月6万溶かす人のことを昔は理解できなかったけど、今なら分かる。あれと同じです。違いがあるとすれば、こっちのガチャは回すたびに仕事が進むところくらいで。 だからこそ、というべきか。ゲーム感覚で6万払っている人間ですら、フェイブル5の従量課金だけは「いやそれは無理」となった、という話でした。 おまけ:AIは通信費なのか、掃除機なのか 最後に、今回の一件でぼんやり考えたことを書き留めておきます。 毎月6万円をAIに払う暮らしをしていて実感するのは、AIがもう「あると便利な道具」を超えて、インフラになり得るレベルまで来ているということです。社会のあちこちでAIが組み込まれて、私たちはその上で成り立っている。ロボットも含めて、AIなしでは回らない社会にどんどんなっていくんでしょう。 個人の側でも、私のようにお金を払って日常のタスクを任せる人は、これからどんどん増えていくはずです。感覚としては、お手伝いさんを雇う贅沢というより、掃除機に近い。掃除機がないと掃除がままならないように、AIがないと日常が回らない。そういう位置づけの道具になりつつあるのを、毎日の実感として感じています。 そこでぼんやり考えるのが、AIは暮らしの中でどの位置に収まるんだろう、ということです。道路は国が作る。通信は道路にかなり似たインフラだけど、国が配ることはなくて、各自が通信費を払って使う。掃除機は完全に家電で、国が配るわけがない。当たり前じゃんという話なんですが、じゃあAIはどれに一番近づいていくのか。通信費のような「みんなが個別に払う準インフラ」なのか、掃除機のような「持ちたい人が買う家電」なのか。私の体感は、日に日に通信費のほうへ寄っていっています。 答えの出る話ではないですが、月6万円の請求と引き換えに、その移り変わりのど真ん中を今まさに歩いているようで、ちょっと面白くもあるのです。 ※ゲージの数値は私の環境(Max 20xプラン)での2026年7月3日朝8時時点の実測です。料金・提供条件の公式情報はAnthropic(anthropic.com / support.claude.com)でご確認ください。 参考リンク 前編:フェイブル5が復活したので、布団の中で200ドルプランの解約を相談した話 Anthropic公式 Claude Fable 5ページ (API価格の一次情報) Claudeサポートセンター (プロモーション条件・使用クレジットの案内)

公開: 2026年7月3日 · Toshihiko Arai
同じサイン波に3種類の音量の輪郭(エンベロープ)をかぶせると、オルガン風・ピアノ風・ストリングス風に聞こえ分かれる図

同じサイン波が「ピアノらしく」も「オルガンらしく」もなる。音の顔を決めるADSRエンベロープ

プログラムで音を鳴らしたことのある方なら、覚えがあるかもしれません。サイン波を作って、440Hz、音量もほどほどに設定して再生する。すると聞こえてくるのは「ピー」という、体温計の電子音のような味気ない音です。周波数は合っている。音量も合っている。それなのに、どうやっても「楽器の音」に聞こえない。 まず足りないのは、波形の複雑さではありません。じつは同じサイン波のまま、あるひとつの要素を変えるだけで、その「ピー」の性格はがらりと変わり、オルガン風、ピアノ風、ストリングス風と、楽器の「らしさ」の方向がはっきり聞き分けられるようになります。変えるのは音量の時間変化、つまり音の鳴りはじめから消えるまでの「輪郭」です。 本題に入る前に、少しだけこの連載のことを。楽器の音そのものをプログラムで作る技術はサウンドプログラミングと呼ばれますが、書籍も事例も昔からとても少ない分野です。筆者は10年以上前、自作の音感トレーニングアプリ Harmonize にピアノやオルガンの音を持たせる必要に迫られて、数少ないC言語の教科書を頼りにソフトシンセを組み上げました。このアプリで鳴る楽器音は録音の再生ではなく、今もすべてプログラムがその場で合成しています。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play そのとき夢中で学んだ仕組みは、いま読み返しても色あせずに面白いので、頭の整理を兼ねて、音の鳴るデモを添えながら少しずつ記事にしていくことにしました。第1回に選んだのは、仕組みの単純さのわりに効果が劇的な、この「輪郭」の話です。 耳は波形より「輪郭」を聴いている 楽器の音をレコーダーで録って波形を眺めると、二つの層が見えます。ひとつは中身の細かい振動。1秒間に数百回も揺れる波そのもので、これが音の高さや音色の成分を担います。もうひとつは、その振動を外側から包む大きなふくらみ、音量の移り変わりです。 上の図は、中身の波をぜんぶ同じサイン波にそろえて、外側の輪郭だけを取り替えたものです。左は鳴らしている間ずっと同じ大きさを保ち、離した瞬間すっと消える。これはオルガンやリコーダーの鳴り方です。真ん中は叩いた瞬間がいちばん大きく、あとはただ減っていく。鍵盤を押さえたままでも音がだんだん消えていく、ピアノの鳴り方です。右はふわっと立ち上がって、指を離してもふわっと余韻が残る。ストリングスやシンセパッドの鳴り方です。 中身が同じでも、この輪郭が違えば、耳は別の系統の音だと聞き分けます。逆もまた然りで、楽器音の録音から立ち上がりの部分を切り落として聴かせると、何の楽器か当てるのが急に難しくなることが古くから知られています。音色というと倍音の配合、つまり波形の中身を思い浮かべますが、楽器の「顔」の第一印象を決めているのは、それと同じくらい、この音量の時間変化なのです。 ただし、正直に言っておくと、輪郭が担うのは音の顔の半分です。もう半分は、やはり波形の中身、倍音の配合が握っています。サイン波は倍音をひとつも持たない、いわば具のないスープのような素朴きわまりない波なので、輪郭をどれだけ作り込んでも「ピアノらしい鳴り方」「ストリングスらしい鳴り方」という性格までしか近づけません。本物のピアノの太い響きや弦楽器のつやを出すには、ノコギリ波のような倍音たっぷりの波形が要ります。このあとのデモでも、どのプリセットも系統ははっきり聞き分けられるのに、本物と呼ぶには物足りないはずです。その物足りなさの正体が倍音で、これはこの連載で回をあらためて取り上げます。今回は「中身が最弱のサイン波ですら、輪郭だけでここまで性格が変わる」という、輪郭の効きの大きさを確かめるのが主題です。 この輪郭のことを、サウンドプログラミングではエンベロープと呼びます。英語で封筒のことで、波を外から包み込む線だからです。冒頭の「ピー」が楽器に聞こえなかった理由も、これで説明がつきます。あの音にはエンベロープがなかった、正確には「スイッチを入れた瞬間に最大、切った瞬間にゼロ」という長方形の輪郭しかなかったのです。長方形の輪郭を持つ楽器は自然界にほぼ存在しないので、耳は機械の音だと判定します。 輪郭を4つの数で描く ── ADSR では、その輪郭をプログラムでどう表すか。世界中のシンセサイザーが採用している定番の型があります。頭文字をとって ADSR と呼ばれる、たった4つの数です。 鍵盤を押すと、音量はゼロから最大まで駆け上がります。この立ち上がりにかける時間がアタック(A)。最大に達したあと、少し落ち着いて巡航高度に降りてくるまでの時間がディケイ(D)。そのまま押し続けている間に保たれる音量がサステイン(S)。そして指を離してから音が消えるまでの余韻の時間がリリース(R)です。 ここでひとつだけ、つまずきやすい点を先に押さえておきます。A・D・R は「時間」ですが、S だけは「高さ」です。サステインは押している間じゅうずっと続くので、何秒という長さを持ちません。持つのは「最大の何割の音量で鳴り続けるか」という水位だけ。ADSR のつまみが4本並んでいるとき、3本は秒数で1本だけパーセントなのは、このためです。 この4つの数で、さきほどの3つの輪郭がそのまま書けます。オルガンはアタックほんの数ミリ秒、サステイン100%。押した瞬間から離す瞬間まで一定で鳴る、ほぼ長方形の輪郭です。ピアノはアタック数ミリ秒、ディケイたっぷり1秒あまり、そしてサステイン0%。押しっぱなしでも行き着く先がゼロなので、音は自然に消えていきます。ストリングスはアタックに数百ミリ秒かけてふわっと入り、サステイン7割ほどで歌い、リリースにもたっぷり時間をとって余韻を残す。輪郭の言葉に翻訳すると、楽器の個性が4つの数字に畳み込まれるのです。 減り方は「まっすぐ」より「割合」が自然 もうひとつ、輪郭の質感を決める隠し味があります。ディケイやリリースで音が減っていくとき、どんなカーブで減るかです。 いちばん素直なのは直線で減らすことですが、これを耳で聴くと、機械がフェーダーを一定速度で下げているような、妙に人工的な感じがします。実際のピアノの弦や鐘の余韻は、毎瞬「いま残っている音量の同じ割合」ずつ減っていきます。最初にぐっと減って、小さくなってからは長々と尾を引く。グラフに描くと上の図の青い線、いわゆる指数カーブです。人間の耳は音量を割合で感じ取る性質があるので、割合で減る音は「一定のペースで静かになっていく」と聞こえて、これが自然に響きます。 冒頭で紹介した Harmonize のソフトシンセも、エンベロープはこの指数カーブで実装しています。さらにピアノ音色では、音量だけでなく音の明るさ(フィルタ)にも同じ形のエンベロープを掛けていて、鳴った瞬間だけ倍音が豊かで、減衰とともにこもっていくという本物のピアノの振る舞いを真似ています。エンベロープは音量専用の道具ではなく、明るさや音程など「時間とともに変わってほしいもの」全般に掛けられる、汎用の輪郭なのです。 プログラムではこう書く では、これを実際にどう実現するのか。仕組みは拍子抜けするほど単純で、1サンプルごとに、波形の値へエンベロープの値を掛けるだけです。44100分の1秒ごとに $\text{出力} = \text{env} \times \sin(\cdot)$ という掛け算を繰り返す。サウンドプログラミングとしての本体は、この $\text{env}$ という変数(0〜1)を時間とともにどう動かすかに尽きます。 まず「毎瞬、同じ割合で減る」という指数カーブを数式にすると、音量 $V$ は時定数 $\tau$(タウ、減衰の速さを決める秒数)を使って $$V(t) = V_0 e^{-t/\tau}$$ と書けます。「残っている量に比例して減る」($dV/dt = -V/\tau$)という関係の解が、ちょうどこの形になります。そしてこれをプログラムに落とすときは、指数関数を毎回計算する必要はありません。「目標値との差を、毎サンプル一定の割合だけ詰める」という1行が、そのまま指数カーブになります。 /* 教科書式のエンベロープ処理(C風の擬似コード) */ for (n = 0; n < length; n++) { env += (target - env) * coef; /* 目標へ指数カーブで近づく */ output[n] = env * sin(2.0 * M_PI * f0 * n / fs); /* 波形に掛けるだけ */ } 面白いのは、A・D・S・R の4局面がぜんぶこの同じ1行で回ることです。鍵盤が押されたら target = 1.0(アタック)、最大に達したら target = サステイン値(ディケイ)、離されたら target = 0.0(リリース)。切り替えるのは目標値だけで、カーブの計算は一切変わりません。近づく速さは coef が受け持ち、時定数 $\tau$ とサンプリング周波数 $f_s$ から $\text{coef} = 1 - e^{-1/(f_s \tau)}$ と決めます(小さいほどゆっくり)。なお、立ち上がりだけは直線にする実装もよくあります。アタックは時間が短く、カーブの違いが耳ではほとんど分からないからです。 ...

公開: 2026年7月2日 · Toshihiko Arai
ハッシュの円環にキーとサーバーが並び、サーバー追加で一部の区間だけ担当が変わる図

サーバーを増やしても「大引っ越し」させない。コンシステント・ハッシュ法(担当は円環の場所で決める)

Webサービスの裏側でサーバーを複数並べるとき、いちばんよく見かけるのは、全サーバーに同じ内容を複製しておき、負荷分散装置(ロードバランサ)がアクセスを適当に振り分ける構成です。どのサーバーも同じものを持っているので、誰が受けても答えられる。データ全体が1台に収まるうちは、これが単純で強いやり方です。 ところが、1台に収まらなくなると話が変わります。分かりやすいのがキャッシュです。毎回データベースに問い合わせると遅いので、一度取り出した答えを高速なサーバーのメモリに覚えさせておく仕組みですが、メモリの量には限りがあります。同じ内容を複製する方式では、サーバーを何台並べても覚えられる量は1台分のまま。量を増やすには、複製ではなく分けて持つしかありません。3台に分ければ容量は3倍——その代わり、複製のときには存在しなかった問題が生まれます。 それが担当決めです。データには user:1001 のような名前(キー)が付いています。あるキーを保存するとき、そして後で読みに来たとき、どちらも同じサーバーに行き着かなければ意味がありません。全キーの置き場所を台帳に記録するやり方は、キーが何億もあると台帳自体が重荷になるので、できればキーを見ただけで担当が計算で決まる決め方が欲しい。 自然に思いつくのは、ハッシュ関数を使う方法です。ハッシュ関数というのは、どんなデータからでも決まった範囲の数値をひとつ作る計算のことで、同じ入力からは必ず同じ数値が出ます。念のため付け加えると、これは「違う入力なら必ず違う数値になる」という意味ではありません。別々のキーが偶然同じハッシュ値を引き当てる「衝突」はあり得ます。ただ今回の用途では、たまたま同じ数値になったキーたちが同じサーバーに置かれるだけなので、衝突があっても仕組みは壊れません。キーのハッシュ値をサーバー台数 n で割った余りを担当番号にすれば、どのキーの置き場所も計算だけで定まる。台帳いらず、計算一発、しかもキーはほぼ均等にばらけます。実際、これで気持ちよく分散できます——台数が変わらないうちは。 1台増やしただけで、ほぼ全部が引っ越す アクセスが増えてきたので、サーバーを3台から4台にしたとしましょう。担当の決め方は「% 3」から「% 4」に変わります。すると何が起きるか。 同じキー、同じハッシュ値なのに、割る数が変わっただけで余りの答えはほぼ全部変わります。上の例では10個中9個。データは1バイトも変わっていないのに、置き場所だけがそっくり入れ替わってしまうのです。 これはキャッシュにとって致命傷になりえます。置き場所が変わるということは、読みに行った先のサーバーがそのデータを持っていないということ。つまり増設した瞬間、ほぼ全アクセスがキャッシュミスになり、問い合わせが後ろのデータベースへ一斉に流れ込みます。負荷を下げたくてサーバーを足したのに、足した瞬間にいちばん重い負荷がかかる。本末転倒です。データ本体を分けて持つ分散ストレージなら、今度は実データの大移動が走ります。 冷静に考えると、動く必要があるのは全体のおよそ4分の1だけのはずです。新しい1台が受け持つぶんを渡せば、残りの4分の3は今の場所のままでいい。この「新入りに渡すぶんだけ動かして、あとは触らない」を台帳なしでやってのけるのが、今回の主役コンシステント・ハッシュ法です。 担当を「計算式」ではなく「場所」で決める 余り方式の弱点は、担当の決め方そのものの中に台数 n が入っていることです。式に n が入っているから、n が変わるたびに全キーの答えが変わる。そこで発想を変えて、担当の決め方から n を追い出します。 ハッシュ値の範囲(0〜最大値)を、端と端がつながった一周の円盤だと思うことにします。キーは、そのハッシュ値の場所にポツンと置く。そしてここがミソなのですが、サーバーも「サーバー名のハッシュ値」の場所に立てて、キーと同じ円盤に載せてしまうのです。そのうえで、キーの担当はこう決めます——その場所から時計回りに歩いて、最初に出会うサーバー。 決め方はこれだけです。円環の端(0)をまたいでも、そのまま一周続けて歩きます。結果として、各サーバーは「自分の手前の区間」をまるごと受け持つ形になります(図の外側の帯)。 ここで、何を・どんな関数でハッシュするのかも押さえておきます。入力はデータの中身ではなく名前(キー)です。user:1001 というキー文字列、URL、ファイル名——読みに行く時点ではまだ中身を手にしていないのだから、名前から場所が計算できなければ意味がありません。サーバー側も「10.0.0.5:6379」のような名前をハッシュして立てます。関数のほうは、速くて出力がまんべんなくばらけるものなら何でもよく、暗号としての強度は要りません。実際、memcached 向けの ketama は MD5 を、Cassandra は MurmurHash3 という高速ハッシュを使っています(MD5 は偽造への耐性という意味では破られた古い関数ですが、キーをばらまくだけの用途なら今も現役です)。この記事のビジュアライザも FNV-1a という軽量ハッシュで動いています。 この決め方のどこにも「台数 n」が出てこないことに注目してください。キーの位置はキーのハッシュ値だけで、サーバーの位置もサーバー名のハッシュ値だけで決まっていて、ほかのサーバーが何台いようが一切関係ありません。台数が式から消えたことが、次の一手で効いてきます。 増やしても、動くのは「新入りの手前」だけ では、サーバーDを追加してみます。やることは、Dのハッシュ値の場所に新しい旗を立てるだけです。 変わるのは、Dの手前の区間だけです。この区間にいるキーは、これまで「時計回りで最初のサーバー」がCだったのが、手前にDが割り込んだのでDに変わる。それ以外の場所では、時計回りに歩いて最初に出会うサーバーが以前と同じなので、担当も同じままです。キーもサーバーも誰ひとり場所を動いていない。新しい旗が1本立って、境界がひとつ増えただけなのです。 引っ越すのは新入りが受け持つ区間ぶん、つまり全体のおよそ n 分の 1。さきほど「理想はこれ」と言った形が、そのまま実現しています。サーバーの削除は逆再生で、旗が1本抜けると、その区間を時計回りの次のサーバーが丸ごと引き取ります。故障でサーバーが落ちたときも、その分担が自動的に隣へ流れるだけで、ほかのキーは無傷。サーバーの顔ぶれが変わる前提のシステムと、とことん相性がいい決め方です。 偏りは「分身」でならす ── 仮想ノード ただし、これで万事解決とはいかないのが現実の面白いところです。旗の立ちどころはハッシュ任せなので、運が悪いと偏ります。 3台がたまたま円環の近い場所に立ってしまうと、1台だけが円盤の大半を受け持つことになります。余り方式なら保証されていた「ほぼ均等」が、場所方式では崩れることがあるわけです。 そこで使われるのが仮想ノードという工夫です。サーバーAを円環上の1点ではなく、「A-1」「A-2」…と名前を変えてハッシュした複数の点——いわば分身——として撒きます。分身が増えるほど担当は細切れに混ざり合い、合計の持ち分はならされていきます。実際のシステムでは1台あたり数十〜数百個の分身を置くのがふつうです。分身にはもうひとつ利点があって、サーバーを追加したときの引っ越し元が特定の1台に集中せず、全員から少しずつ分けてもらう形になります。増減時の引っ越しが約 n 分の 1 で済む性質は、そのまま変わりません。 触って確かめる ── ビジュアライザ ここまでの話を、実際にサーバーを増減させながら確かめられるビジュアライザを用意しました。60個のキー(●)は最初から最後まで同じもので、方式を切り替えると同じキーたちの担当がどう決まるかが変わります。「+ サーバー追加」を押すと、直前の操作で引っ越したキーが赤い縁で残るので、余り方式でほぼ全部が赤くなるのと、円環方式で一部の区間だけが赤くなるのを見比べてみてください。手元で試したところ、3台から4台への増設で引っ越したキーは、余り方式が60個中47個(78%)、円環方式は12個(20%)でした。下の比較表には「いまの構成に1台足したら何個動くか」が3方式並ぶので、数字でも確認できます。円環方式で「各サーバーの担当キー数」が偏っていくのを見つけたら、仮想ノードに切り替えてみてください。 うまく表示されないときは、ビジュアライザを別タブで開く 。 ...

公開: 2026年7月2日 · 更新: 2026年7月3日 · Toshihiko Arai
夜明けの布団の中でスマホの光を頼りにAIへ相談する人と、料金プランを表す抽象的なモチーフのアイキャッチ画像

フェイブル5が復活したので、布団の中で200ドルプランの解約を相談した話

フェイブル5が今日から使えるようになりました。6月に突然止まって、約3週間ぶりの復活ですね。めでたい。 めでたいんだけど、衝撃の事実がありまして。サブスクで使えるのは7月7日までで、それ以降は従量課金になるそうです。フェイブル5だけね。トークン使用量によってお金を取られる感じ。 で、それがどんなものなのかが分からないので、非常に不安なわけです。 布団の中で相談してみた 今日は5時台に起きて作業してたんだけど、8時ぐらいに頭がすっきりしなくて、もう一度寝たいなと布団に入った。結局寝れなかったので、布団の中からタラタラとAIに相談してました。 相談したのはこういうプラン。 200ドルプランを100ドルプランに戻す。で、浮いた100ドル分を従量課金に回してフェイブル5を使う。今まで通りの感覚でいけるのか? 答えは「桁が合わない」でした。 理由を聞くと、まず私みたいに毎週リミットの90%まで使い倒す人間の消費量は、API換算だと月に数百〜数千ドル相当になるらしい。実際、海外のヘビーユーザーがサブスク内で1日110ドル相当を溶かしたという報告もあるそうで。つまり月200ドルで買ってるのは、従量課金なら軽く10倍以上の値段がつくリソースなんだよね。 しかもフェイブル5の単価はOpus 4.8の2倍(入力$10/出力$50 per 100万トークン)で、おまけにトークン食いが激しい。さらに言えば、100ドルプランの枠は200ドルプランの4分の1しかないから、フェイブル5以前にOpusでの日常作業だけで枠が飛ぶ。 なるほどね。ダウングレード案、あっさり撃沈です。 で、実際に使ってみた感想 今日使った限り、劇的に良くなったという感じはしないね。 結局、指示の勘違いだったりはあるから、そこまでのメリットはもしかしたらないのか、あるのか。 ただこれ、調べてみると私の体感はそんなに外れてないらしい。Anthropic自身が「タスクが長く複雑になるほどフェイブル5のリードは大きくなる」という言い方をしていて、逆に言えば短めのタスクでは差に気づきにくいモデルなんだよね。実務者の評価でも「日常業務の大半はOpus 4.8のほうがコストと速度のバランスで優れる。重く長いタスクだけフェイブル5に振るのが正解」という声が出ている。 私の使い方はまさにその「短め・構造化済み」の側なんだと思う。というのも私の場合、AIにとって使いやすい小道具を先に自前で用意してあるんだよね。テストとかビルドとかリリースみたいな定型作業を、シェルスクリプトで一発で叩けるようにしてある。あとはVPSサーバー上でエージェントをバッチ処理で走らせる仕組み、いわゆるエージェントランナーも組んである。要するに、AIには「考えること」だけをさせて、それ以外の段取りは全部道具側に持たせている状態。そういう骨組みを固めたうえで指示を出してるから、Opusでもかなりスムーズに行ってる。フェイブル5の売りである「自分で計画して、自分でテストして、自分で検証する」の部分を、私はすでに自前の仕組みでやらせているわけで。だとすれば差が出にくいのも道理かなと。まあこれは私の仮説で、逆に数日がかりの自律タスクを丸投げしたら評価が変わる可能性は全然あります。まだそういう使い方はしてないので。 あともうひとつ、復活後のフェイブル5には強化された安全分類器が入っていて、日常的なコーディングやデバッグでも無害なリクエストが誤検知されることがあり、その場合は裏でOpus 4.8が応答する仕組みになってるらしい。95%以上のセッションはフェイブル5自身が答えているそうだから多くはないんだろうけど、「今日の返事、実はOpusだった」可能性も一応あるわけです。 結論というほどでもない結論 200ドルプランは維持 従量課金のクレジットは最初50ドルだけ入れて、減り方を観察 フェイブル5は「ここぞ」というタスク専用 いずれにせよ、今後はどんどんフェイブル5以上のものが出てくるだろうし、フェイブル5がサブスクラインで使える日も来るだろうとは思うんだよね。Anthropic自身も「容量の問題で、できるだけ早くサブスクに戻したい」と言ってるみたいだし。だから時間が経つにつれ、ますます使いやすくなる未来は予想できます。焦って課金を積む必要はないかなと。 それはさておき ハーモナイズの課金をしてくれた人がいました。デベロッパー支援で1000円のコーヒー、真ん中のやつですね。ありがとうございます。日本の方でした。こういうのがあると、布団から出る気力が湧きます。 そのハーモナイズも含めて、最近はこのクロードで自分のアプリ開発をどんどん進めています。フェイブル5だ従量課金だと気を揉んではいますが、結局いちばん実感しているのは、AIと組んでからの開発の速さです。ハーモナイズを直しながら、別のアプリの機能追加も並行で走らせて……という具合に、一人でも複数の作業が同時にどんどん片づいていく。朝に布団の中で相談したことが、その日のうちに形になっていることも珍しくありません。値段のことで文句を言いつつも、この身軽さはもう手放せないな、というのが正直なところです。 ハーモナイズは無料で遊べる音感トレーニングアプリです。よかったら触ってみてください。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play 参考リンク Anthropic公式 Claude Fable 5ページ (API価格、セーフガード、Opus 4.8へのフォールバックの説明) Anthropic公式ニュース (提供再開と7月7日までのスケジュールに関する発表) Claudeサポートセンター (使用クレジットの購入・自動チャージの設定方法) ※料金・提供条件は2026年7月2日時点の情報です。

公開: 2026年7月2日 · Toshihiko Arai
グリッド上を探索が波紋状に広がり、スタートからゴールへ最短経路が引かれる図

地図の最短経路を「賢く」探す。ダイクストラとA★(探索を絞る当て推量)

カーナビが目的地までの道順を一瞬で出す。ゲームの敵が壁を避けて最短でこちらへ寄ってくる。どちらも裏では「最短経路を求める」という同じ問題を解いています。 素朴にやるなら、スタートからゴールまでの道を片っ端から試して、いちばん短いものを選ぶことになります。ですが分かれ道のたびに候補が枝分かれするので、少し広い地図になっただけで組み合わせは天文学的にふくれ上がり、まともに終わりません。それでも地図アプリやゲームが即座に答えを返すのは、全部の道を試したりせず、「見込みのある候補から順に確定していく」というひとつの発想を使っているからです。その王道がダイクストラ法で、もう一段賢くしたのが A★(エースター)です。順に見ていきます。 いちばん安い候補から確定する ── ダイクストラ 地図をマス目で考えます。1マス進むのにコストがかかり、ふつうの道は $1$、ぬかるみのように通りにくいマスは $5$、というふうに重みをつけます。目指すのは、スタートからゴールまでのコストの合計がいちばん小さい道です。 鍵になるのが $g$ という値です。$g$ は経路の名前でも「今の勝者」でもなく、マスひとつひとつが持つ数字で、「そのマスへ、今わかっている範囲でいちばん安く行くといくらか」を表します。スタート地点は $g=0$。そこから隣の上下左右を見ると、4マスそれぞれに $g=1$ が付きます。ここで大事なのは、この4マスから1つを選ぶわけではないこと。4マス全部が「暫定 $g=1$」の候補(フロンティア)として並びます。 ダイクストラがやるのは、フロンティアに並んだマスのうち $g$ がいちばん小さいものを1つ取り出して確定し、その隣の $g$ を計算して候補に加える、という手順の繰り返しだけです。たとえば $g=1$ の上のマスを確定したとします。するとその周り(さらに上・左上・右上)に $g=2$ が付きます。では次に進むのはその $g=2$ のどれか——ではありません。候補にはまだ手つかずの「右」「左」「下」が $g=1$ のまま残っています。$1 < 2$ なので、次に確定するのは上方向ではなく、この $g=1$ のマス。「上へ一歩進んだのに、また元の近くへ戻って右を確定する」ように見えるのは、このためです。全体でいちばん安い候補を選ぶ、という一点を守っているだけなのです。 そして、ここがダイクストラの心臓部です。1つのマスは、まわりの確定が進むにつれて、いくつものルートから「このマス経由なら $g$ はいくつ」という候補を何度も受け取ります。同じマスに、複数の $g$ の候補が届くのです。そのたびにやることは一つだけ。いま持っている値より安ければ書き換える(これを更新、あるいは緩和と呼びます)。だから各マスの $g$ は、届いた候補のうち最小値へとだんだん下がっていき、そのマスが全体でいちばん安くなった瞬間に、最小のまま確定します。この「複数の候補が届いて、小さい方だけが生き残る」一手が、遠回りと近道を正しく選び分けているからくりの全部です。のちほど、ぬかるみのところで、この様子を実際の数字で見ます。 だから確定は $g$ の小さい順に進みます。$g=1$ のマスをぜんぶ埋めてから $g=2$ の輪、その次が $g=3$ の輪……と、同じ $g$ のマスがひと組ずつ確定していきます。池に石を落としたときの輪とまったく同じで、輪の半径がそのままコスト $g$。この輪が外へ育っていくのが「波紋」の正体です。 最短が保証されるのは、いちばん安いマスにはもうそれ以上安く来る方法が残っていないからです。他のマスは今より高いコストからしか伸びてこないので、確定した瞬間にその $g$ は最終値として動きません。 では、壁があるとどうなるか。壁は「コストが高い」のではなく、そもそも通れないマスです。計算に入る前に、道として除外されます。だから突き抜けはできず、上か下へ回り込むしかありません。回り込むぶん、壁の裏のマスは $g$ が大きくなり、確定も遅れます。結果として、同じ $g$ の輪は壁のところで食い込み、波紋が壁を避けて、へこみながら広がっていくように見えます。 ...

公開: 2026年7月2日 · 更新: 2026年7月3日 · Toshihiko Arai
一本道と輪からなるρ字型の上を、カメとウサギが進む図

カメとウサギで「輪」を見つける。フロイドの循環検出(追加メモリO(1))

自分で書いたプログラムが固まって、いつまで待っても返ってこない——その原因が無限ループだった、という経験はないでしょうか。やっかいなのは、次へ、次へ とデータをたどっていく類の処理が、輪にハマったときです。 たとえば、連結リストをたどるコードを書いたとします。本来は末尾(null)で終わるはずが、データが壊れて途中のノードが前のノードを指していたら、たどる処理は終点にたどり着けず、同じ環をぐるぐる回り続けます。連結リストに限らず、「ある値から次の値を計算して進む」「状態 A から次の状態へ遷移する」——こうした「次をたどる」ロジックを自分で書くとき、どこかで輪に入り込んでいないかは、コードを眺めただけでは分かりません。 この記事で扱うのは、そういう自分のコードに「隠れた輪」が無いかを、たどりながら確かめる方法です。具体的には、連結リストが壊れて循環していないかのチェック、配列の重複探し(配列を「次をたどる写像」とみなします)、あるいは自分の反復ロジックがループに落ちないかをテストで確かめたいとき——どれも同じ道具ひとつで済みます。 素朴にやるなら、「訪れた場所を全部メモ(集合)しておいて、すでに来たことのある場所に戻ったら輪あり」とすればいい。正しく動きますが、訪れた数だけメモリ($O(n)$)が要ります。 この記事の主役は、メモを一切持たず——変数2つ、追加メモリ $O(1)$——で輪を見つける、フロイドの循環検出(カメとウサギ)です。最後に、ブラウザだけで動くビジュアライザで実際に動かし、前回の玉突き方式 の無限ループにも当てはめてみます。 まず、輪のある列は「ρ(ロー)の形」になる 「現在の場所 → 次の場所」が毎回決まっている列をたどると、形は必ずρ(ギリシャ文字のロー)のようになります。輪に入るまでの一本道(しっぽ)と、ぐるぐる回る輪です。 輪の最後のノードの「次」は、輪の入口に戻ります。だから一度入ると二度と出られず、回り続ける。やりたいのは「輪があるか」、できれば「輪の入口はどこか」を知ることです。 カメとウサギ:ポインタ2つで追いつく フロイドの方法は、拍子抜けするほど単純です。同じスタートから、速さの違う2つのポインタを走らせます。 🐢 カメ:1歩ずつ進む 🐇 ウサギ:2歩ずつ進む 第1幕。 輪が無ければ、ウサギが先に終点(行き止まり)へ着きます。輪があれば、ウサギは輪の中をぐるぐる回り、いつか必ずカメに追いつきます。なぜなら、2匹が輪に入ったあとは、ウサギはカメに毎ステップ1つずつ近づくから。差が1ずつ縮むので、飛び越してすれ違うことはなく、必ずピッタリ一致します(差が1ずつしか減らない以上、輪の長さが偶数でも奇数でも必ず 0 を通ります)。一致したら、輪がある証拠です。 第2幕。 ここがエレガントなところ。出会った地点にカメを残し、もう1つのポインタを起点(スタート)に戻して、今度は両方とも1歩ずつ進めます。すると、ちょうど輪の入口で再会します。これで輪の始まりまで分かります。 function detectCycle(start, next){ let slow = start, fast = start; // 第1幕:出会うまで(出会わずに終点へ着けば輪は無い) do { slow = next(slow); // 1歩 fast = next(fast); if(fast === null) return null; // 2歩(1歩ごとに終点を確認) fast = next(fast); if(fast === null) return null; } while(slow !== fast); // 第2幕:起点と出会った地点から1歩ずつ → 入口で再会 let p = start; while(p !== slow){ p = next(p); slow = next(slow); } return p; // 輪の入口 } 使っているのは slow と fast(と p)という変数だけ。列がどれだけ長くても、メモリは増えません。これが $O(1)$ の意味です。 なぜ第2幕で入口に揃うのか 一本道の長さを $\mu$、輪の長さを $\lambda$ とします。第1幕でカメが進んだ距離を $d$ とすると、ウサギはその倍の $2d$ 進んで同じ場所にいるので、その差 $d$ は輪の長さ $\lambda$ の倍数です。つまりカメは「$\lambda$ の倍数ぶん」だけ余計に回った場所にいる。ここから、起点と出会い地点をそれぞれ1歩ずつ進めると、ちょうど $\mu$ 歩で両者が入口に重なります。証明は1行で、紙とペンでも追えます。 ...

公開: 2026年7月1日 · 更新: 2026年7月3日 · Toshihiko Arai
固定長の配列を輪に見立て、head と count で管理するリングバッファの図

配列を「輪」に見立てるリングバッファ。Queue の先頭削除からシフトを消す

プログラムで「先に入れたものから順に取り出す」キュー(Queue)を作りたい場面は、よくあります。ログのバッファ、イベントの待ち行列、センサー値の直近 N 件——どれも Queue です。 JavaScript なら、配列ひとつで素直に書けます。push で末尾に足し、shift で先頭を取り出す。 class NaiveQueue { constructor(cap){ this.cap = cap; this.data = []; } enqueue(x){ if(this.data.length >= this.cap) return false; this.data.push(x); return true; } dequeue(){ return this.data.length ? this.data.shift() : undefined; } } 短くて分かりやすい。でもこのやり方には、要素数が増えると効いてくる弱点があります。 弱点:先頭を取り出すたびに、全部が前へずれる 配列の先頭(index 0)を取り出すと、残りの要素はすべて1つ前へ詰め直されます。2番目が先頭へ、3番目が2番目へ……と、全員が席を1つずつ移動する。これが shift の正体です。 要素が4個なら4個ぶん、1万個なら1万個ぶん動かす。つまり dequeue 1回のコストが、入っている要素数に比例して重くなります($O(n)$)。enqueue は末尾に足すだけなので軽いのに、取り出す側が足を引っ張るわけです。 中身は1バイトも変わらないのに、「席をずらす」ためだけに大量のコピーが走る。動かしたいのは「どこが先頭か」という目印だけのはずです。 リングバッファ:配列を「輪」に見立てる そこで、固定長の配列を用意して、それを輪のように使います。これがリングバッファです。 考え方はシンプルで、配列を伸び縮みさせる代わりに、2つの数だけを動かします。 head … いま先頭がある位置(次に取り出す場所) count … いま入っている要素数 enqueue は、空いている次の位置 (head + count) % cap に書いて count を1増やすだけ。dequeue は、head の中身を読んで head を1つ先に進め、count を1減らすだけ。要素は1つも動きません。 右端まで埋まったら、% cap(剰余)のおかげで自動的に 0 番へ戻ります。輪をぐるぐる回りながら、書く位置と読む位置の目印だけがずれていく。配列そのものは最初から最後まで同じ大きさのままです。 class RingQueue { constructor(cap){ this.cap = cap; this.data = new Array(cap); this.head = 0; this.count = 0; } enqueue(x){ if(this.count >= this.cap) return false; // 満杯 this.data[(this.head + this.count) % this.cap] = x; this.count++; return true; } dequeue(){ if(this.count === 0) return undefined; // 空 const x = this.data[this.head]; this.data[this.head] = undefined; // 取り出した参照を残さない(GCのため) this.head = (this.head + 1) % this.cap; // 先頭の目印を進めるだけ this.count--; return x; } } shift のような全体の詰め直しがどこにもありません。dequeue がやるのは足し算と剰余だけなので、入っている要素数に関係なく一定の手数($O(1)$)で終わります。 ...

公開: 2026年7月1日 · 更新: 2026年7月3日 · Toshihiko Arai
配列が左へ回転していく様子を表した手書き風の図

配列を「その場で」左に回す。別配列なしで回転する2つの方法

配列を「左に k 個ぶん回したい」場面があります。 [A, B, C, D, E, F, G] を 2 つ左に回すと [C, D, E, F, G, A, B]。先頭の何個かが、そっくり後ろへ回り込むイメージです。リングバッファ 、文字列のローテーション、表示行の巻き戻し——地味ですがよく出てきます。 素直にやるなら、回したあとの並びを別の配列に書き出して、それで元を置き換えれば終わりです。ただ、それだと配列がもう1本ぶん、まるごとメモリに要ります。動かしたいのは「順番」だけなのに、です。 この記事では、別配列を一切使わず、その場(in-place)で配列を回す方法を2つ紹介します。3回ひっくり返すだけのリバーサル法と、$\gcd(n,k)$ 本の鎖で穴を歩かせるジャグリング法。最後に、両方をブラウザだけで動くビジュアライザで実際に触って確かめます。 この記事は、画像をリネームだけで並び替える「玉突き方式」 の続編にあたります。あちらで出てきた「穴を歩かせる」手口が、今回のジャグリング法でそのまま効いてきます。 まず、素朴なやり方とその代償 いちばん分かりやすいのは、結果を別配列へ書くやり方です。 function rotateLeftCopy(a, k){ const n = a.length; k = ((k % n) + n) % n; // 負の k も正の回転量に正規化 const out = new Array(n); for(let i = 0; i < n; i++){ out[i] = a[(i + k) % n]; // マス i には「元の (i+k) 番目」が来る } return out; } これは正しく動きます。ただ、out という長さ $n$ の別配列が要ります。要素が重かったり本数が多かったりすると、この「もう1本」が効いてきます。 やりたいのは並べ替えだけ。中身は1つも変わりません。だったら、追加メモリ $O(1)$(作業用の変数1個ぶん)で済ませたい。ここからが本題です。 方法1:リバーサル法(3回ひっくり返すだけ) 最初に紹介するのは、拍子抜けするほど単純な方法です。左回転 k は、次の3回の部分反転に分解できます。 前半 k 個を反転する 後半 n−k 個を反転する 全体を反転する [A, B, C, D, E, F, G] を k=2 で左に回す例で追ってみます。 ...

公開: 2026年7月1日 · 更新: 2026年7月3日 · Toshihiko Arai