プログラミング

【abcjsアプリ】スケール(音階)を表示するプログラム

ジャズなどでアドリブをする際、ある程度スケール(音階)の知識が必要になります。
ひとつのスケールを覚えてもキーが変わると楽器の運指も変わるので、できればすべてのキーのスケール一覧が欲しいところですが、そうするとけっこう膨大な数になるので譜面を用意するのも一苦労です。

ギターなどはスケールを形で覚えてフレットを左右にずらすだけで移調できるので便利ですが、ピアノや管楽器などは大変だと思います(脳内トランズポーズできる達人もいらっしゃいますが)。

私も含めて、いろいろなスケール(音階)を瞬時に調べたい方は多いのではないかと思い、JavaScriptでこのアプリを作った次第です。

使用ライブラリは「abcjs」

使用ライブラリはABC記譜法で楽譜を作成できる「abcjs」です。
ABC記譜法はネット上で楽譜を記述する場合は利点が多く、主流となっています。

ちなみに、途中まで「VexFlow」というライブラリで作っていたのですが、abcjs と比べて楽譜を表示するまでのコードの記述量が多く複雑になってしまうので、やむなく変更した経緯があります。
ライブラリの選定はちゃんとしないといけませんね。

abcjs は abcjs で、チュートリアルのとおりに記述しても動かないなど、手間取ることもありました(ボヤキ)。

スケール(音階)表示のロジック

abcjs の使い方はとても簡単で、フロントエンドの方ならあくびをしながら実装できます。
私も「楽勝でしょ」とこのアプリを作り始めたのですが、スケール切替のロジックを考えるのに意外と苦労しました。

音階って単純な規則で並んでいるようで微妙に複雑なところがあることに改めて気がつきました。

HTML でキー(調) とスケールを設定して JavaScript で表示する

ユーザーが設定するのはキー(調) とスケールで、JavaScriptで値を取得します。
特に難しいことはありません。

<form method="post" action="#" id="register">
        <select id="postKey">
            <option value=0>C</option>
            <option value=2>D</option>
            <option value=4>E</option>
            <option value=5>F</option>
           ・・・以下略・・・
    <option value=11>B</option>

            <!-- #系 -->
            <option value=1>C#</option>
            <option value=3>D#</option>
           ・・・以下略・・・
    <option value=10>A#</option>

            <!-- ♭系 -->
            <option value=12>D♭</option>
            <option value=14>E♭</option>
           ・・・以下略・・・
    <option value=21>B♭</option>
        </select>
        <select id="postScale">
            <option value="0">イオニアン</option>
            <option value="1">ドリアン</option>
            <option value="2">フリジアン</option>
            ・・・以下略

        </select>
        <button>表示</button></p>
    </form>

ノート(音)を配列に入れる

ノート(音)を配列に格納し、指定スケールに必要な要素を取り出すプログラムにします。

ここで♯と♭の切り替えに悩みました。
例えば ド#(C#)の場合、調によってはレ♭(D♭)で表記する必要があります。
同じ音程でも場合に応じて2種類の使い分けをしないといけません。

これは下記のように二重配列で対応することにしました。キー(調)によって#か♭を指定する仕組みです。
(abcjsは「^」で#、「_」で♭、「,」でオクターブ下、小文字表記でオクターブ上になります)

const notes = [
    ['C,','C,'],['^C,','_D,'],['D,','D,'],['^D,','_E,'],['E,','E,'],['F,','F,'],
    ['^F,','_G,'],['G,','G,'],['^G,','_A,'],['A,','A,'],['^A,','_B,'],['B,','B,'],
    /* 12番目からが一般的な音域のドレミファソラシド */
    ['C','C'],['^C','_D'],['D','D'],['^D','_E'],['E','E'],['F','F'],
    ['^F','_G'],['G','G'],['^G','_A'],['A','A'],['^A','_B'],['B','B'],
    ['c','c'], ・・・以下略

];

Cより下の音は表示しないにも関わらず Cを12番目に持ってきたのは、配列操作の都合上オクターブ下のドレミファ…が必要だったからです。
(省略していますが、実は2オクターブ上までドレミファ…が続いています)
これがなければもっとスマートなプログラムになるんですが…。

配列からどのようにノート(音)を取り出すか

スケールってたくさんあります。

ポピュラーな「ドレミファ…」のスケールであればイオニアン、ドリアン、リディアン…、それらにC、D、Eなどのキーがあるので、イオニアンから派生するスケールだけでも77種類になります。
(詳しくは割愛しますが、「スケール7通り × キー11通り」です)
とてもではないですが、手作業で記述する気はおきません。

ですがスケールというのは「基準となるスケールの何番目からスタートするか」でしかありません。
例えば Dドリアンであれば、Cイオニアンの2番目からスタートしたスケールですし、Eフリジアンは Cイオニアンの3番目からスタートしたスケールです。

つまり、基準となるスケールを変数として設定して、それを抽出・並び替えるプログラムを書けば良いわけです。

以下は単純な「ドレミファソラシド」のスケールである「イオニアンスケール(メジャースケール)」を基準にしたものです。

//POST で発火
$('#register').on('submit', function() {
    //HTML の Form から Key と Scale を取得
    const postKey = Number($('#postKey').val()); // value の値を取得, Number でゼロサプレス

    if (postKey >= 12) {          // 12以上が♭系
        var key = postKey +1;   //♭系の場合、#postKey から 1を足して#系の同音程に合わせる
    } else {
        var key = postKey +12;  // 基音になる Cは、配列 notes の12番目のため、♮#系は12を足す
    }

    let postScale = $('#postScale').val(); 

    /* Ionian 以外のスケールを Ionian とした場合のキーに変更する
        例えば選択したスケールが Dドリアンの場合、Cイオニアンの2番目からなので、
        var key から2を引いて(Dは key=2)キーCにする(Cは key=0) */
    if (postScale == 1) {           // Dorian
        key = key -2;
    } else if (postScale == 2) { // Phrygian
        key = key -4;
    } else if (postScale == 3) { // Lydian
        key = key -5;
    } else if (postScale == 4) { // Mixolydian
        key = key -7;
    } else if (postScale == 5) { // Aeolian
        key = key -9;
    } else if (postScale == 6) { // Locrian
        key = key -11;
    }

    // #♭ 表示切替
    const sign = key;
    if (postKey >= 12){
        if (sign == 8   || // Key = A♭
            sign == 13     // Key = D♭
       ・・・以下略
        ) {
            var b = 1;
        } else {
            var b = 0;
        }
    } else {
        if (sign == 17  || // Key = F
            sign == 5   || // Key = F
            sign == 8   || // Key = A♭
            sign == 10     // Key = B♭
       ・・・以下略
        ) {
            var b = 1;
        } else {
            var b = 0;
        }
    }
    
    /* スケールを構築
       [b]で二次元配列の #♭ を選択する */
    const ionian = [
        notes[key][b], notes[key +2][b], notes[key +4][b], notes[key +5][b], 
        notes[key +7][b], notes[key +9][b], notes[key +11][b],notes[key +12][b],
        notes[key +14][b], notes[key +16][b], notes[key +17][b], notes[key +19][b],
        notes[key +21][b], notes[key +23][b]
    ];

    // 選択した基音から8音取り出す
    var scale = ionian.slice(postScale, postScale +8);

    let setScale = scale[0] + scale[ 1] + scale[ 2] + scale[ 3] + " " + scale[ 4] + scale[5] + scale[6] + scale[7];
    let renderScale = "\nT:" + key + " Ionian\n" + setScale + "|\n";

    ABCJS.renderAbc("score", renderScale);
    return false; // 画面遷移を防ぐ
});

DOM操作にはやっつけで jQueryを使っていますが、最近のトレンドを鑑みて Vue.jsに書き直したいところです。
また、読みにくいところも多いので、今後修正を加えていきたいと思います。

以上の記述でとりあえずメジャースケール(とそこから派生するマイナースケール)は表示できることを確認しました。
次はオルタードなどの頻出スケールや、TAB譜表示にも着手しようと思います。

とりあえず以上です。配列操作のいい練習になりました。
本記事に需要がありそうなら詳細を追記していきたいと思います。