3
3

フェーズドアレーレーダーの原理。ステレオスピーカーを用いた擬似的な音のビーム形成のゲームです。

Last updated at Posted at 2024-09-30

ステレオスピーカーを用いた擬似的な音のビーム形成

スクリーンショット 2024-10-01 044245.png

フェーズドアレーレーダーの基本的な原理のゲームです。

このコードでできること:
スピーカーから音波をビーム状に発射する擬似シミュレーションを体験できます。
位相差に基づいてビームの方向が変化し、その方向がリアルタイムでキャンバスに描画されます。
マイクで取得した反射音をスペクトログラムとして表示し、音波の反射状況を視覚的に確認できます。

コードの概要:
音波ビームの擬似形成:左右のスピーカーに位相差を持たせることで、音のビームを擬似的に形成します。Math.sin() 関数を使って、時間とともに音の位相を変化させ、音波ビームの方向を変更します。
反射音の検知:マイクからの音声入力を取得し、AnalyserNode を使って周波数データを分析します。反射された音の大きさ(周波数スペクトルの強度)に基づいて、前方に障害物があるかどうかを人力で判断します。

参考文献。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>フェーズドアレーレーダー擬似シミュレーション</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin-top: 50px;
        }
        #status {
            margin-top: 20px;
            font-size: 20px;
        }
        #canvas {
            border: 1px solid black;
            margin-top: 20px;
        }
        #spectrum {
            border: 1px solid black;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <h1>音波ビームシミュレーション</h1>
    <button id="start">ビーム発射&音の反射検知開始</button>
    <p id="status">ステータス: 待機中</p>
    <canvas id="canvas" width="400" height="200">ビーム方向</canvas>
    <canvas id="spectrum" width="400" height="200">スペクトログラム</canvas>

    <script>
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const oscillatorLeft = audioContext.createOscillator();
        const oscillatorRight = audioContext.createOscillator();

        // 音の周波数を設定(440Hz)
        oscillatorLeft.frequency.value = 440;
        oscillatorRight.frequency.value = 440;

        // ステレオパニング設定
        const panLeft = audioContext.createStereoPanner();
        panLeft.pan.value = -1; // 左スピーカー
        const panRight = audioContext.createStereoPanner();
        panRight.pan.value = 1; // 右スピーカー

        oscillatorLeft.connect(panLeft).connect(audioContext.destination);
        oscillatorRight.connect(panRight).connect(audioContext.destination);

        let phaseShift = 0; // 位相差の初期値
        const beamAngle = 0; // ビームの角度

        // 位相差を計算し、ビーム方向を操作
        function calculatePhaseShift(angle) {
            const wavelength = 343 / 440; // 音速343m/s, 周波数440Hz -> 波長計算
            const distanceBetweenSpeakers = 0.9; // 左右スピーカーの間隔(30cmと仮定)
            
            const phaseDifference = (2 * Math.PI * distanceBetweenSpeakers * Math.sin(angle * Math.PI / 180)) / wavelength;
            return phaseDifference;
        }

        // ビーム方向とスペクトログラム用のキャンバス設定
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const spectrumCanvas = document.getElementById('spectrum');
        const spectrumCtx = spectrumCanvas.getContext('2d');

        // ビーム方向の描画関数
        function drawBeamDirection(angle) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const beamAngle = angle % 180; // 角度は0〜180度の範囲で表示

            ctx.beginPath();
            ctx.moveTo(canvas.width / 2, canvas.height);
            const x = canvas.width / 2 + 100 * Math.cos(beamAngle * Math.PI / 180);
            const y = canvas.height - 100 * Math.sin(beamAngle * Math.PI / 180);
            ctx.lineTo(x, y);
            ctx.strokeStyle = 'blue';
            ctx.lineWidth = 2;
            ctx.stroke();

            ctx.font = '14px Arial';
            ctx.fillText(`ビーム角度: ${Math.round(beamAngle)}度`, 10, 20);
        }

        // スペクトログラム描画の更新
        function drawSpectrogram(dataArray) {
            spectrumCtx.clearRect(0, 0, spectrumCanvas.width, spectrumCanvas.height);
            const barWidth = (spectrumCanvas.width / dataArray.length) * 2.5;
            let barHeight;
            let x = 0;

            for (let i = 0; i < dataArray.length; i++) {
                barHeight = dataArray[i] / 2;

                spectrumCtx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
                spectrumCtx.fillRect(x, spectrumCanvas.height - barHeight / 2, barWidth, barHeight);

                x += barWidth + 1;
            }
        }

        // 位相差を変化させてビームの向きを更新
        function updatePhaseShift() {
            phaseShift += 0.05; // 位相差の変化
            const beamDirection = phaseShift * 180 / Math.PI; // 位相差からビーム角度を計算

            // 位相差に基づいて左右のスピーカーの位相を設定
            const leftPhaseShift = Math.sin(phaseShift);
            const rightPhaseShift = Math.sin(phaseShift + calculatePhaseShift(beamDirection));

            // 左右スピーカーの周波数を変更して位相差を表現
            oscillatorLeft.frequency.setValueAtTime(440 + leftPhaseShift * 10, audioContext.currentTime);
            oscillatorRight.frequency.setValueAtTime(440 + rightPhaseShift * 10, audioContext.currentTime);

            drawBeamDirection(beamDirection);
        }

        // マイクを使って反射音を検出し、スペクトログラムを表示
        async function detectEcho() {
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                const micSource = audioContext.createMediaStreamSource(stream);
                const analyser = audioContext.createAnalyser();
                micSource.connect(analyser);

                analyser.fftSize = 2048;
                const bufferLength = analyser.frequencyBinCount;
                const dataArray = new Uint8Array(bufferLength);

                // 音の反射を定期的に検出してスペクトログラムに描画
                function analyseAudio() {
                    analyser.getByteFrequencyData(dataArray);

                    // 反射音の大きさを基にスペクトログラム描画
                    drawSpectrogram(dataArray);

                    // 反射音の大きさで障害物の検知を判定
                    const maxVal = Math.max(...dataArray);
                    if (maxVal > 128) {
                        document.getElementById("status").textContent = "反射音検知: 障害物あり";
                    } else {
                        document.getElementById("status").textContent = "ビーム発射中";
                    }

                    requestAnimationFrame(analyseAudio);
                }

                analyseAudio();

            } catch (err) {
                console.error("マイクアクセスエラー: ", err);
            }
        }

        // ビーム発射とマイク反射音検知を開始
        document.getElementById("start").addEventListener("click", () => {
            document.getElementById("status").textContent = "ビーム発射開始";

            // 音波の発射(ビーム形成)
            oscillatorLeft.start();
            oscillatorRight.start();

            // ビームの向きとスペクトログラムの描画を開始
            setInterval(updatePhaseShift, 100);

            // マイクで反射音を検出
            detectEcho();
        });

        window.addEventListener("beforeunload", () => {
            oscillatorLeft.stop();
            oscillatorRight.stop();
        });
    </script>
</body>
</html>

位相差は、ステレオスピーカーシステムで音のビームを形成するために非常に重要な要素です。ビームフォーミング(音波の特定の方向への集中)は、スピーカーから出る音の位相差を利用して達成されます。以下で、位相差の計算方法とその仕組みを説明します。

  1. 位相差とは
    音波の位相は、波のサイクル内での位置を示します。位相差とは、2つの音波が波のサイクルにおいてどれだけずれているかを指します。ステレオスピーカーの場合、スピーカー間でわずかに位相をずらすことによって、音波が特定の方向に集中するビームが形成されます。

以下は位相差を反映させる簡易な例です。


// スピーカー間の距離 (メートル)
const speakerDistance = 0.5;

// 音速 (空気中、メートル/秒)
const soundSpeed = 343;

// 音波の周波数 (Hz)
const frequency = 1000;

// 波長の計算
const wavelength = soundSpeed / frequency;

// ビーム角度 (度)
let beamAngle = 30; // 例として30度

// ラジアンに変換
const theta = beamAngle * (Math.PI / 180);

// 位相差の計算
const phaseDifference = (2 * Math.PI * speakerDistance * Math.sin(theta)) / wavelength;

console.log("位相差: ", phaseDifference, "ラジアン");

// 位相差を使ってスピーカーからの出力を操作
const leftSpeakerPhase = Math.sin(2 * Math.PI * frequency * time);
const rightSpeakerPhase = Math.sin(2 * Math.PI * frequency * time + phaseDifference);

// 左右スピーカーからの音の出力
playSound(leftSpeakerPhase, rightSpeakerPhase);
  1. まとめ
    位相差を使って、音波のビームを制御することができます。
    位相差はスピーカー間の距離、音波の波長、ビームの角度によって決まります。
    この原理は、実際のフェーズドアレイシステム(例えばレーダーや音響ビームフォーミング)と同じ考え方です。
    この方法を使うと、教育的なシミュレーションでステレオスピーカーを用いた簡易的なビームフォーミングの挙動を観察することが可能になります。
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3