指板で音を可視化する！（ Fretboard.js & React ）

# はじめに
自分の尊敬するジャズギタリスト、[高免さんの教則本](https://amzn.asia/d/fmGeS4N)に取り組んでいる中で、「メジャースケール上にダイアトニックコードの構成音が視えるようなツール」が欲しいと思い、作ってみました。
# できたもの
![maj-app-video-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1023843/e8a672d6-b64d-be27-a5a8-4d5991508f44.gif)
指板にスケールの構成音を表示して、そこに音名と度数を表示させています。また、Chord で選んだコードの構成音の1度（ルート）にオレンジ、3, 5, 7度に青の色をつけています。

コードは[こちら](https://github.com/forester3003/maj-scale-chord-tones)
動くものは[こちら](https://forester3003.github.io/maj-scale-chord-tones/)
# 使ったもの
- [moonwave99/fretboard.js](https://github.com/moonwave99/fretboard.js)
指板を表示することができるライブラリはいくつかあったものの、かなり昔からメンテがされていないものばかりでどうしようかと思ったものの、比較的最近までコミット履歴があって[ドキュメント](https://moonwave99.github.io/fretboard.js/index.html)も作られていたこちらを利用することにしました。

- React(TypeScript)
fretboard.js は React 対応しているわけではなかったのですが、せっかくなら React のお勉強を兼ねてこのライブラリを React で使ってやろう！と頑張ってみました。

- Tailwind CSS
そんなに凝ったものを作るつもりもなかったので、CSS フレームワークは Next.js を勉強していたときに触って多少慣れていたこれを使うことにしました。
# コード
Create React App で作成されたものから以下を追加、更新しています。（Tailwind CSS の適用まわりは省略）
```MajScale.tsx
import React, { useEffect, useRef, useState } from "react";
import { Fretboard } from "@moonwave99/fretboard.js";

export default function MajScale() {
  // フレットボードの描画用のref
  const fretboardRef = useRef<HTMLElement>(null);
  // 選択されたルート音とコードのstate
  const [root, setRoot] = useState("C");
  const [chord, setChord] = useState("I_Maj7");
  // 選択状態の画面表示用のstate
  const [selectedValues, setSelectedValues] = useState({
    root: "C",
    chord: "I_Maj7",
  });

  useEffect(() => {
    console.log(root, chord);
    // レンダリング
    if (fretboardRef.current) {
      fretboardRef.current.innerHTML = ""; // すでに要素が存在する場合はクリアする

      // フレットボードの初期化
      const fretboard = new Fretboard({
        el: fretboardRef.current,
        dotSize: 28,
        highlightFill: "whitesmoke",
        width: 960,
      });

      // フレットボードにメジャースケールを描画
      fretboard.renderScale({
        root: root,
        type: "ionian",
      });

      // 音名と度数を表示
      fretboard.style({
        text: ({ interval, note }: { interval: any; note: any }) =>
          interval + "/" + note,
        fontSize: 10,
      });

      // Ionianにおけるダイアトニックコード定義
      const chordsDefinition: { [key: string]: string[] } = {
        I_Maj7: ["1P", "3M", "5P", "7M"],
        II_m7: ["2M", "4P", "6M", "1P"],
        III_m7: ["3M", "5P", "7M", "2M"],
        IV_Maj7: ["4P", "6M", "1P", "3M"],
        V_7: ["5P", "7M", "2M", "4P"],
        VI_m7: ["6M", "1P", "3M", "5P"],
        VII_m7b5: ["7M", "2M", "4P", "6M"],
      };

      const chordComponent: string[] = chordsDefinition[chord];

      chordComponent.forEach((interval, index) => {
        // rootの場合は色を変える
        if (index === 0) {
          fretboard.style({
            filter: { interval: interval },
            fill: "orange",
          });
          // それ以外のインターバルリストに入っているものは色を変える
        } else {
          fretboard.style({
            filter: { interval: interval },
            fill: "skyblue",
          });
        }
      });

      console.log("rendered");
    }
  }, [fretboardRef.current, root, chord]); // 依存配列が変更されるたびに再レンダリングをトリガー

  // ルート音が変更されたときキックされる関数
  const handleRootChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    // rootのstateを更新
    setRoot(event.target.value);
    // 選択された値をstateに保存
    const newRoot = event.target.value;
    setSelectedValues((prev) => ({ ...prev, root: newRoot }));
  };

  // コードが変更されたときキックされる関数
  const handleChordChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    // chordのstateを更新
    setChord(event.target.value);
    // 選択された値をstateに保存
    const newChord = event.target.value;
    setSelectedValues((prev) => ({ ...prev, chord: newChord }));
  };

  return (
    <>
      <div className="flex justify-center items-center mt-4">
        <label className="mr-2">Root:</label>
        <select
          value={root}
          onChange={handleRootChange}
          className="text-gray-800 rounded-md border border-gray-500 focus:outline-none"
        >
          <option value="C">C</option>
          <option value="Db">Db/C#</option>
          <option value="D">D</option>
          <option value="Eb">Eb/D#</option>
          <option value="E">E</option>
          <option value="F">F</option>
          <option value="Gb">Gb/F#</option>
          <option value="G">G</option>
          <option value="Ab">Ab/G#</option>
          <option value="A">A</option>
          <option value="Bb">Bb/A#</option>
          <option value="B">B</option>
        </select>
        <label className="mx-2">Chord:</label>
        <select
          value={chord}
          onChange={handleChordChange}
          className="text-gray-800 rounded-md border border-gray-500 focus:outline-none"
        >
          <option value="I_Maj7">IMaj7</option>
          <option value="II_m7">IIm7</option>
          <option value="III_m7">IIIm7</option>
          <option value="IV_Maj7">IVMaj7</option>
          <option value="V_7">V7</option>
          <option value="VI_m7">VIm7</option>
          <option value="VII_m7b5">VIIm7b5</option>
        </select>
      </div>
      <div className="text-center mt-4">
        <span>{selectedValues.root}メジャースケール上の</span>
        <span>{selectedValues.chord.replace(/_/g, "")}</span>
      </div>
      <div className="overflow-x-auto">
        <figure ref={fretboardRef} className="flex-none"></figure>
      </div>
    </>
  );
}

```

```App.tsx
import './App.css';
import MajScale from './MajScale';

function App() {
  return (
    <div className="App">
      <MajScale />
      <div className='p-5 my-5 mx-5 rounded-lg bg-yellow-100 shadow'>
        <p>
          Root から指板上に表示するメジャースケールを選択します。
        </p>
        <p>
          Chord から表示されているメジャースケールから指定したダイアトニックコードの構成音に色を付けます。
        </p>
      </div>
    </div>
  );
}

export default App;

```
# 最後に
## コーディングについて
自分が React に慣れていないのもあって、React 化はだいぶ大変でしたが AI の力を大いに借りて、なんとか想定していたものを作ることができました。React への理解も進んだ気がします。
## ギターの練習について
この Web アプリで「Fメジャーキーのときに外さない音を指板上で見ながら、今鳴っているコードの構成音がどこか見る」訓練ができるようになったので、コード感のあるソロを取れるように頑張っていきたいと思います！