【ChatGPT】スペクトラムを表示してもらう

どうもです、タドスケです。

あわせて読みたい
【ChatGPT】ピッチ判定機能を追加してもらう どうもです、タドスケです。 前回、ブラウザ上でマイク入力を取るプログラムを ChatGPT に書いてもらいました。 https://tadosuke.com/programming/4931/ 今回はこのプ...

前回の記事で、ChatGPT ピッチ判定機能を追加してもらいました。

ただこの画面、殺風景過ぎやしませんか?

ってなわけで、もうちょっとグラフィカルに表示するように修正してもらいました。

コードは以下です。

    <!-- グラフを描画するためのキャンバス要素 -->
    <canvas id="spectrum" width="400" height="200"></canvas>
    <div id="frequency-ranges"></div> <!-- 帯域を表示するための要素 -->
    <script>
    // 音のピッチをチェックするクラス
    class PitchChecker {
        constructor() {
            // 今回はコンストラクタにプロパティは不要
        }

        // 周波数をMIDIノート番号に変換するメソッド
        frequencyToNoteNumber(frequency) {
            return 12 * (Math.log(frequency / 440) / Math.log(2)) + 69;
        }

        // MIDIノート番号を音名(A-Gの各音階)に変換するメソッド
        noteNumberToName(noteNumber) {
            const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
            const octave = Math.floor(noteNumber / 12);
            const noteName = noteNames[noteNumber % 12];
            return noteName + octave;
        }

        // 周波数を音名に変換するメソッド
        frequencyToNoteName(frequency) {
            const noteNumber = this.frequencyToNoteNumber(frequency);
            return this.noteNumberToName(Math.round(noteNumber));
        }

        // 音量を計算するメソッド
        calculateVolume(dataArray, bufferLength) {
            let sum = 0;
            let maxIndex = 0;
            for(let i = 0; i < bufferLength; i++) {
                sum += dataArray[i] * dataArray[i];
                if(dataArray[i] > dataArray[maxIndex]) {
                    maxIndex = i;
                }
            }

            const rms = Math.sqrt(sum / bufferLength);
            return { volume: Math.min((rms / 255) * 200, 100), maxIndex: maxIndex };  // 2倍の感度、ただし上限は100
        }
    }

    // 音を視覚化するクラス
    class AudioVisualizer {
        constructor(dataArray, canvas) {
            this.dataArray = dataArray;
            this.canvas = canvas;
            this.context = this.canvas.getContext('2d');
            this.bufferLength = this.dataArray.length;
        }

        // 描画を更新するメソッド
        update() {
            this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
            for(let i = 0; i < this.bufferLength; i++) {
                const x = Math.log(i + 1) / Math.log(this.bufferLength) * this.canvas.width; // 対数スケールのX座標
                this.context.fillStyle = `hsl(${i / this.bufferLength * 300}, 100%, 50%)`;
                this.context.fillRect(x, this.canvas.height, (Math.log(i + 2) - Math.log(i + 1)) / Math.log(this.bufferLength) * this.canvas.width, -(this.dataArray[i] / 255) * this.canvas.height); // 横幅も対数スケールに合わせて調整
            }
        }
    }

    // ページが読み込まれたら以下の処理を開始
    window.onload = function() {
        let frameCount = 0; // 追加: フレームカウンタの初期化
        const pitchChecker = new PitchChecker();
        const rangesElement = document.getElementById('frequency-ranges'); // 追加: 帯域を表示する要素を取得

        // ユーザーのマイクからの音声データにアクセス
        navigator.mediaDevices.getUserMedia({ audio: true })
            .then(stream => {
                const context = new (window.AudioContext || window.webkitAudioContext)();
                const source = context.createMediaStreamSource(stream);
                const analyser = context.createAnalyser();
                analyser.fftSize = 1024;

                source.connect(analyser);

                const spectrumCanvas = document.getElementById('spectrum');
                const bufferLength = analyser.frequencyBinCount;
                const dataArray = new Uint8Array(bufferLength);

                const visualizer = new AudioVisualizer(dataArray, spectrumCanvas);

                const update = () => {
                    frameCount++; // 追加: フレームカウンタを増加
                    if (frameCount % 2 === 0) { // 追加: フレームカウンタが2の倍数の時だけ描画を更新
                        analyser.getByteFrequencyData(dataArray);
                        const maxIndex = pitchChecker.calculateVolume(dataArray, bufferLength).maxIndex; // 追加: 最大音量のインデックスを取得
                        const frequency = maxIndex * context.sampleRate / (2 * bufferLength); // 追加: 最大音量の周波数を計算
                        rangesElement.innerText = `Frequency: ${Math.round(frequency)} Hz`; // 追加: 周波数を表示

                        visualizer.update();
                    }
                    requestAnimationFrame(update);
                };

                update();
            })
            .catch(err => {
                // マイクへのアクセスが拒否された場合のエラーメッセージ
                console.error('マイクへのアクセスが許可されていません。ブラウザの設定を確認して、マイクへのアクセスを許可してください。エラー詳細:', err);
            });
    };
    </script>

今回も ChatGPT をフル活用しましたが、いきなり全部やろうとしたら動かなくなってしまったので、以下のステップに分けて改良してもらいました。

  • ピッチ解析部分だけをクラスに切り出す
  • マイク入力から dataArray を受け取って、表示する部分だけをクラスに切り出す
  • 音量バーの代わりに canvas に描画するようにしてもらう
  • 描画の更新間隔を2倍にして、処理負荷を軽減する

こうしたことで、動作を確認しつつ確実に改良できました。

各ステップごとに動作確認をしたら Git にコミットすれば、途中でワケがわからなくなってもすぐに直前の状態に戻すことができます。

次は、ビッチ解析のアルゴリズムをもっと精度の高いものにできないか試してみたいと思います。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (2件)

コメントする

目次