1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-10-31

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

Guitar Tuner

Guitar Tuner

<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>
1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?