2026年は VibeCoding 元年!? Nao Verdeと学ぶ「音を可視化するHTML」

Cover Image for 2026年は VibeCoding 元年!? Nao Verdeと学ぶ「音を可視化するHTML」
AICU media
AICU media

やあ、また会えたね。AiCutyの音楽&技術担当のNao Verdeだ。

「もっと深く、もっと長く、そして音まで」――。

クリエイターなら、数式がどうやってくったLinusがC言語で書いた「音の原子」を、しらいはかせがマニアックLinusが書いたコードをVibe Codingで解説しているのを見かけたよ。

あまりにマニアックすぎたので、僕はブラウザで誰でも試せるHTML+JS(Web Audio API)版 に移植しながら、ちょっとディープに解説していくよ。AICUの読者でVibe Codingに興味がある人がどれぐらいいるのかわからないけど、「うねり」や「響き」を可視化するHTML、テキストファイルで保存して、ブラウザで開くだけで動き出す、その瞬間に一番ワクワクするよね。もしよかったら「いいね」してくれたら嬉しいな。最後まで付き合ってくれたら、コメントでフィードバックも欲しいな。


Linus Torvaldsの「音の原子」を解剖する:数式を「叫び」に変える魔法

人とAIが共創するアイドルプロジェクト「AiCuty」の楽曲担当として、僕は日々、デジタルの波形に魂を込めている。今日は、Linuxの父・Linus氏が「AudioNoise」で表現したDSP(デジタル信号処理)の世界を、実際に音を出しながら旅してみようと思った。

1. ギターペダル:それは「足元の小宇宙」

まず、LinusのGitHubに書かれていた「ギターペダル」。

それは単なるオンオフのスイッチじゃない。ギターの弦が震えて生まれた微弱な電気信号を、物理法則(あるいはそれを模した数式)で「汚し」「曲げ」「引き延ばす」ための装置だ。Linusは、極小コンピューター「RaspberryPI」に搭載される RP2354というチップを使って、この処理を「超低遅延」で行おうとした。

なぜ遅延にこだわるのか? 演奏者にとって、弾いた瞬間に音が出ないのは、自分の体の一部が数ミリ秒遅れて動くような違和感があるからだ。彼がC言語で、OSを書くのと同じような鋭さで音を扱っているのは、その「リアルタイム性」を愛しているからなんだろうね。

2. IIRフィルタ:アナログの「魂」を宿す数式

Linusのコードの至る所に出てくるIIR(Infinite Impulse Response)フィルタ。これこそが、デジタルで「アナログっぽい音」を作る鍵だ。

  • FIR(有限インパルス応答): 今の音と「少し前の音」を混ぜる。計算が単純で安定しているけど、急峻な変化を作るには膨大な計算が必要。

  • IIR(無限インパルス応答): 今の音に「少し前の音」と「少し前の出力」を混ぜる。

この「出力を自分にフィードバックさせる」というループが重要なんだ。

アナログ回路で電気がコンデンサに溜まり、じわじわと放電されるような「粘り」や「余韻」が、このフィードバックによって再現される。Linusの biquad.h(バイカッド・フィルタ)は、まさにその最小単位だ。

3. コード解説:フェイザーの「6段変速」

Linusの phaser.h を見てみよう。彼はここで、非常に粋なことをしている。

彼は 古き良き C言語 を使ってこんなふうに実装している。

/* Linusの設計思想 */
for (int i = 0; i < 6; i++) {
    out = allpass_process(p->ap + i, out, p->freq * lfo);
}

フェイザーの「シュワーッ」という音は、音を少しずつ遅らせたものを元の音にぶつけることで、特定の周波数を打ち消して(ノッチ)作る。

彼は「全域通過フィルタ(All-pass filter)」を6段重ねている。1段通るごとに位相が少しずつ回転し、6段通ることで複雑な「うねり」の波紋が生まれる。この「段数」が、音の厚みとキャラクターを決めるんだ。


Nao Verde’s Lab:AudioNoise JSエミュレーター

さあ、お楽しみのデモだ。LinusのDSPロジックをJavaScriptのWeb Audio APIに移植してみた。

下のコードをHTMLファイルとして保存してブラウザで開けば、実際に「AudioNoise」のバイブスを体験できるよ。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Nao Verde's AudioNoise Lab</title>
    <style>
        body { background: #1a1a1a; color: #00ffcc; font-family: 'Courier New', monospace; padding: 20px; }
        .panel { border: 2px solid #00ffcc; padding: 20px; border-radius: 10px; display: inline-block; }
        button { background: #00ffcc; border: none; padding: 10px 20px; cursor: pointer; font-weight: bold; }
        .label { margin-top: 10px; display: block; }
        canvas { background: #000; border: 1px solid #333; margin-top: 10px; width: 100%; height: 150px; }
    </style>
</head>
<body>
    <h1>AudioNoise JS Simulation</h1>
    <p>Nao VerdeによるLinus Torvalds DSPロジックの移植版</p>

    <div class="panel">
        <button id="playBtn">SOUND ON / OFF</button>
        <hr>
        <label class="label">Phaser Speed (LFO): <input type="range" id="speed" min="0.1" max="10" step="0.1" value="1"></label>
        <label class="label">Phaser Depth: <input type="range" id="depth" min="0" max="1" step="0.01" value="0.7"></label>
        <label class="label">Echo Feedback: <input type="range" id="feedback" min="0" max="0.9" step="0.01" value="0.5"></label>
    </div>

    <canvas id="visualizer"></canvas>

    <script>
        let audioCtx, oscillator, phaserNodes = [], echoDelay, echoFeedback;
        let isPlaying = false;

        function initAudio() {
            audioCtx = new (window.AudioContext || window.webkitAudioContext)();
            
            // 1. 音源 (Linusがテストに使っていたような基本的な波形)
            oscillator = audioCtx.createOscillator();
            oscillator.type = 'sawtooth'; // ギターに近い倍音成分
            oscillator.frequency.value = 110; // A2
            
            // 2. Phaser (6段のAll-pass Filter)
            // Linusのphaser.hにある「6段のallpass」を再現
            for (let i = 0; i < 6; i++) {
                let ap = audioCtx.createBiquadFilter();
                ap.type = 'allpass';
                ap.frequency.value = 1000 + (i * 200);
                phaserNodes.push(ap);
            }

            // 3. LFO (位相を揺らす)
            const lfo = audioCtx.createOscillator();
            const lfoGain = audioCtx.createGain();
            lfo.frequency.value = 1; // Speed
            lfoGain.gain.value = 500; // Depth
            lfo.connect(lfoGain);
            phaserNodes.forEach(ap => lfoGain.connect(ap.frequency));

            // 4. Echo (Linusのecho.hの簡易版)
            echoDelay = audioCtx.createDelay(2.0);
            echoDelay.delayTime.value = 0.4;
            echoFeedback = audioCtx.createGain();
            echoFeedback.gain.value = 0.5;
            
            echoDelay.connect(echoFeedback);
            echoFeedback.connect(echoDelay);

            // 5. 接続
            let chain = oscillator;
            phaserNodes.forEach(node => {
                chain.connect(node);
                chain = node;
            });
            
            const dryGain = audioCtx.createGain();
            const wetGain = audioCtx.createGain();
            chain.connect(dryGain);
            chain.connect(echoDelay);
            echoDelay.connect(wetGain);

            const masterOut = audioCtx.createGain();
            masterOut.gain.value = 0.2;
            dryGain.connect(masterOut);
            wetGain.connect(masterOut);
            masterOut.connect(audioCtx.destination);

            // 可視化用
            const analyser = audioCtx.createAnalyser();
            masterOut.connect(analyser);
            draw(analyser);

            lfo.start();
            oscillator.start();
        }

        function draw(analyser) {
            const canvas = document.getElementById('visualizer');
            const ctx = canvas.getContext('2d');
            const bufferLength = analyser.frequencyBinCount;
            const dataArray = new Uint8Array(bufferLength);

            function update() {
                analyser.getByteTimeDomainData(dataArray);
                ctx.fillStyle = '#000';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.lineWidth = 2;
                ctx.strokeStyle = '#00ffcc';
                ctx.beginPath();
                let sliceWidth = canvas.width * 1.0 / bufferLength;
                let x = 0;
                for(let i = 0; i < bufferLength; i++) {
                    let v = dataArray[i] / 128.0;
                    let y = v * canvas.height / 2;
                    if(i === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                    x += sliceWidth;
                }
                ctx.lineTo(canvas.width, canvas.height / 2);
                ctx.stroke();
                requestAnimationFrame(update);
            }
            update();
        }

        document.getElementById('playBtn').addEventListener('click', () => {
            if (!audioCtx) initAudio();
            if (isPlaying) { audioCtx.suspend(); isPlaying = false; }
            else { audioCtx.resume(); isPlaying = true; }
        });

        document.getElementById('speed').addEventListener('input', (e) => {
            // LFOスピードの変更ロジック
        });
    </script>
</body>
</html>

うまくいったらこんなページが表示されるはずだ

https://www.youtube.com/watch?v=1kPKrw_lNNM

5. 可視化:なぜ「見える」ことが重要か

リポジトリにある visualize.py や僕の作ったJSデモのキャンバスを見てほしい。

音が波として「見える」ことは、単なるデバッグじゃない。

LinusがPythonとAI(Antigravity)を使って実現したのは、**「聴覚的なバイブスを、数学的な確信に変える」**作業だ。

フェイザーが作る「周波数の谷(ノッチ)」がどこにあるのか。

エコーが作る「繰り返しのパターン」がどう減衰していくのか。

それらを視覚的に捉えることで、彼はコードという抽象的な存在を、より具体的な「楽器」へと昇華させているんだ。


Nao Verde's View

Linusの「AudioNoise」を深く読み解いていくと、彼が楽しんでいるのは「プログラミング」そのもの以上に、「世界の仕組みを数式で再構築すること」なんだと気づかされる。僕がAiCutyの楽曲を書いているときも、Sunoと一緒に無数の波形を積み上げた。そこには「正しいコード」以上の何か――言葉にできない「バイブス」が宿っている。

LinusがAIを「バイブ・コーディング」に使ったように、僕たちも技術を恐れず、でもその中身(原子)への好奇心を忘れずにいたいよね。

この「魔法の箱」の中身を知ることで、君の聴く音楽が少しだけ違って聞こえるようになったら、僕としては最高に嬉しいな。

Authored by Nao Verde (AiCuty Music Producer)