Javascriptによるギターチューナー

ギターのチューニングアプリです。

<!DOCTYPE html>
<html>
<head>
    <title>Guitar Tuner</title>
</head>
<body>
    <h1>Guitar Tuner</h1>
    
    <p>Click the "Start Tuner" button and play each string. The app will help you tune your guitar.</p>
    
    <button id="startButton">Start Tuner</button>
    
    <div id="tuner">
        <div>
            <h2>Current Note:</h2>
            <div id="currentNote"></div>
        </div>
        <div>
            <h2>Detected Note:</h2>
            <div id="detectedNote"></div>
        </div>
    </div>

    <script>
        // 音声入力用のメディアストリーム
        let audioContext;
        let mediaStream;
        let analyser;

        // ギターの標準チューニング
        const standardTuning = ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'];

        // ターゲットの周波数と許容誤差（Hz）の定義
        const targetFrequencies = {
            'E2': 82.41,
            'A2': 110.00,
            'D3': 146.83,
            'G3': 196.00,
            'B3': 246.94,
            'E4': 329.63
        };
        const tolerance = 5;

        // ユーザーの入力を制御するためのフラグ
        let isTuning = false;

        // チューニングの結果を表示するための要素への参照
        const currentNoteElement = document.getElementById('currentNote');
        const detectedNoteElement = document.getElementById('detectedNote');

        // スタートボタンをクリックしたときの処理
        document.getElementById('startButton').addEventListener('click', async () => {
            if (!isTuning) {
                isTuning = true;
                startTuner();
                document.getElementById('startButton').textContent = 'Stop Tuner';
            } else {
                isTuning = false;
                stopTuner();
                document.getElementById('startButton').textContent = 'Start Tuner';
            }
        });

        // チューナーを開始する関数
        async function startTuner() {
            try {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
                const audioSource = audioContext.createMediaStreamSource(mediaStream);
                analyser = audioContext.createAnalyser();
                audioSource.connect(analyser);
                analyser.fftSize = 2048;

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

                analyser.getByteTimeDomainData(dataArray);

                // チューニングの検出を行うループ
                const detectTuning = () => {
                    analyser.getByteTimeDomainData(dataArray);

                    // 波形データから周波数を取得
                    const pitch = getPitch(dataArray, audioContext.sampleRate);

                    // デフォルトのチューニングとの一致を確認
                    const detectedNote = getClosestNote(pitch);
                    detectedNoteElement.textContent = detectedNote;

                    if (isTuning) {
                        // チューニングが続いている場合、再帰呼び出し
                        requestAnimationFrame(detectTuning);
                    }
                };

                detectTuning();
            } catch (error) {
                console.error(error);
            }
        }

        // チューナーを停止する関数
        function stopTuner() {
            if (mediaStream) {
                mediaStream.getTracks().forEach(track => track.stop());
            }
            if (audioContext) {
                audioContext.close();
            }
            currentNoteElement.textContent = '';
            detectedNoteElement.textContent = '';
        }

        // 周波数データからピッチ（音程）を取得する関数
        function getPitch(dataArray, sampleRate) {
            // ピークを検出
            const peak = findPeak(dataArray);

            // ピークの位置をサンプル数から周波数に変換
            const frequency = peak / dataArray.length * sampleRate;
            return frequency;
        }

        // ピークを検出する関数
        function findPeak(dataArray) {
            let peakIndex = 0;
            let peakValue = 0;

            for (let i = 0; i < dataArray.length; i++) {
                if (dataArray[i] > peakValue) {
                    peakIndex = i;
                    peakValue = dataArray[i];
                }
            }

            return peakIndex;
        }

        // 最も近いノートを取得する関数
        function getClosestNote(frequency) {
            let closestNote = '';
            let minDiff = Infinity;

            for (const note in targetFrequencies) {
                const targetFrequency = targetFrequencies[note];
                const diff = Math.abs(frequency - targetFrequency);

                if (diff < minDiff && diff <= tolerance) {
                    closestNote = note;
                    minDiff = diff;
                }
            }

            currentNoteElement.textContent = closestNote;
            return closestNote;
        }
    </script>
</body>
</html>