俺は静的"コード"解析がしたい。

この記事は [リンク情報システム](https://www.lis.co.jp/) の「2019 Tech Connect Summer」のリレー記事です。
engineer.hanzomon のグループメンバーによってリレーされます。

[2019 Tech Connect Summer インデックスはこちら](https://qiita.com/dgkz/items/2862d5e6b77203a126b3)

1日目らしいです。頑張ります。
こんなニッチな記事が初っ端で大丈夫か。

俺の静的コード解析
------------

### 静的コード解析とは

>静的コード解析 (static code analysis) または静的プログラム解析 (static program analysis)とは、
>コンピュータのソフトウェアの解析手法の一種であり、実行ファイルを実行することなく解析を行うこと。

[Wiki:静的コード解析](https://ja.wikipedia.org/wiki/%E9%9D%99%E7%9A%84%E3%82%B3%E3%83%BC%E3%83%89%E8%A7%A3%E6%9E%90)

ちょっと何言ってるかわかんない。(わかるけど)

###コードと言ったら和音だろ！！！
(ここでいうコードはcodeではなくchord)
(エンジニアらしからぬ発言)

ということで**「ギターフレット上で押さえたところからギターコードを解析する」**プログラムを作成してみたいと思います。


要件定義
------------

- ギター上で押さえているフレットを指定できるGUI(音も鳴る)
- 上記で指定した情報からコードを解析
- そして俺にっこり

こんな感じですかね。

GUI(音も鳴る)
------------
自分で「音も鳴る」とか書いておいて、全然どうすればいいかわかんないという悪夢
そんなリアルな音は求めてないので今回はMIDI音源を鳴らします。

### MIDI音源を鳴らすテスト
####GUI作成

1. 以下のようなGUIを作成(まさにC#)
![MIDITEST01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317702/cd70263e-f1c8-4d05-f11a-e145b3629e1a.png)

2. 以下のようなクラスを作成


```C#
class MIDISound : IDisposable
{
    //-------------------------------------------------
    // Windows Multimedia API
    [DllImport("Winmm.dll")]
    extern static uint midiOutOpen(ref IntPtr lphmo, uint uDeviceID, uint dwCallback, uint dwCallbackInstance, uint dwFlags);

    [DllImport("Winmm.dll")]
    extern static uint midiOutClose(IntPtr hmo);

    [DllImport("Winmm.dll")]
    extern static uint midiOutShortMsg(IntPtr hmo, uint dwMsg);

    private const uint MMSYSERR_NOERROR = 0;
    private const uint MIDI_MAPPER = 0xffffffff;
    //--------------------------------------------------

    private IntPtr hMidi;

    public MIDISound()
    {
        MidiApiOpen();
    }

    public void Dispose() => MidiApiClose();

    private bool MidiApiOpen()
    {
        bool bRet = false;

        //MidiデバイスのOpen
        if (midiOutOpen(ref this.hMidi, MIDI_MAPPER, 0, 0, 0) == MMSYSERR_NOERROR)
        {
            bRet = true;
            midiOutShortMsg(this.hMidi, 0x19c0);	// 音色を定義 アコースティックギター（スチール弦）0x19(25)
        }

        return bRet;
    }

    private bool MidiApiClose()
    {
        bool bRet = false;

        //MidiデバイスのClose
        if (midiOutClose(this.hMidi) == MMSYSERR_NOERROR)
        {
            bRet = true;
        }

        return bRet;
    }

    public void NoteOn(List<byte> keys)
    {
        byte ch = 0;
        byte velocity = 0x7f;

        foreach (byte key in keys)
        {
            uint msg;
            msg = (uint)((velocity << 16) + (key << 8) + 0x90 + ch);
            midiOutShortMsg(this.hMidi, msg);
        }
    }

    public void NoteOff(List<byte> keys)
    {
        byte ch = 0;
        byte velocity = 0x7f;

        foreach (byte key in keys)
        {
            uint msg;
            msg = (uint)((velocity << 16) + (key << 8) + 0x80 + ch);
            midiOutShortMsg(this.hMidi, msg);
        }
    }
}
```

3. 以下のように呼び出す

```C#
MIDISound midi = new MIDISound();

//C#
List<byte> Chord = new List<byte>() { 0x31,0x36,0x3A };

private void button1_MouseDown(object sender, MouseEventArgs e)
{
    midi.NoteOn(Chord);
}

private void button1_MouseUp(object sender, MouseEventArgs e)
{
    midi.NoteOff(Chord);
}
```

4. ボタンを押下する。

**C#(ドの#)のメジャーコードが鳴りましたね。**
(これがやりたかっただけ)


上記のソースを流用して以下のようなGUIを作成しました。
ラジオボタンをクリックするとMIDI音源が流れます。
以下を使ってギターで押さえた個所をコード分析します。
![GUI01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317702/51f02973-b1ef-214f-07e1-354a1f1ed3db.png)


指定した情報からコードを解析
------------

[ギターコード](http://muuu.jp/chords/)なんて死ぬほどたくさんあるので
「ここがルート音で、次の音がこれだけ離れてて…」なんて解析するのは至極面倒

midiファイルを読み込んだだけでコードの名前当ててくれる何かがあればなぁー
とか思っていたらpythonのmusic21というライブラリでできるようなので使ってみます。

それと同時に前章で作成したGUIからMIDIファイルを作成してみます。

### ・MIDIファイルの生成
MIDIファイルの構造を読み解くために[このページ](https://sites.google.com/site/yyagisite/material/smfspec)を参考にしてプログラムを作成しました。
ってか難しいな…。実装したら予想以上に汚くなりました。

```C#

	private bool makeMidiFile(String midiFilePath)
	{
	    bool ret = false;

	    try
	    {
	        byte[] MThd = new byte[] { 0x4D, 0x54, 0x68, 0x64 };
	        byte[] dataLength = new byte[] { 0x00, 0x00, 0x00, 0x06 };
	        byte[] format = new byte[] { 0x00, 0x01 };
	        byte[] tracknum = new byte[] { 0x00, 0x02 };
	        byte[] trackLength = new byte[] { 0x00, 0x30 };
	        
	        //ヘッダ作成
	        byte[] Header = JoinByteArray(MThd, dataLength, format, tracknum, trackLength);

	        byte[] MTrk = new byte[] { 0x4D, 0x54, 0x72, 0x6B };
	        byte[] TrackEnd = new byte[] { 0x00, 0xFF, 0x2F, 0x00 };
	        byte[] ClockInfo = new byte[] { 0x00, 0xFF, 0x58, 0x04, 0x04, 0x02, 0x18, 0x08 };
	        byte[] TempoInfo = new byte[] { 0x00, 0xFF, 0x51, 0x03, 0x07, 0xA1, 0x20 };

	        byte[] TrackValue = new byte[] { 0x00, 0xC0, 0x19 };

	        //playNotes -> GUIで指定した音
	        foreach (byte note in playNotes)
	        {
	            TrackValue = JoinByteArray(TrackValue, new byte[] { 0x00, 0x90, note, 0x5F });
	        }

	        bool notFirst = false;
	        foreach (byte note in playNotes)
	        {
	            if (notFirst == false)
	            {
	                TrackValue = JoinByteArray(TrackValue, new byte[] { 0x40, 0x90, note, 0x00 });
	                notFirst = true;
	            }
	            else
	            {
	                TrackValue = JoinByteArray(TrackValue, new byte[] { 0x00, 0x90, note, 0x00 });
	            }
	        }

	        //バイナリファイルの内容作成
	        byte[] midifile = JoinByteArray(Header,
	            MTrk, new byte[] { 0x00, 0x00, 0x00, (byte)(ClockInfo.Length + TempoInfo.Length + TrackEnd.Length) }, ClockInfo, TempoInfo, TrackEnd,
	            MTrk, new byte[] { 0x00, 0x00, 0x00, (byte)(TrackValue.Length + TrackEnd.Length) }, TrackValue, TrackEnd);

	        FileStream fs = new FileStream(midiFilePath, FileMode.Create);
	        BinaryWriter bw = new BinaryWriter(fs);

	        bw.Write(midifile);

	        bw.Close();
	        fs.Close();

	        ret = true;
	    }
	    catch (Exception ex)
	    {
	        ret = false;
	    }

	    return ret;
	}

    //複数のbyte配列を結合して一つのbyte配列にまとめる
	private byte[] JoinByteArray(params byte[][] arrays)
	{
	    List<byte> ret = new List<byte>();

	    foreach (byte[] array in arrays)
	    {
	        foreach (byte arr_m in array)
	        {
	            ret.Add(arr_m);
	        }
	    }
	    return ret.ToArray();
	}
```

####汚ねぇソースだ
一応midiファイルを作成することができました。

### ・解析を実行するPythonScriptの作成

1. music21をインストール

    ```    
    > py -m pip install music21
    ```

2.  以下のようなスクリプトを作成

    ```Python
    import music21 as m21
    import sys
    import os
    
    def midiChordAnalysis(path):
    	try:
    		piece = m21.converter.parse(path)
    		bChords = piece.chordify()
    		for thisChord in bChords.recurse().getElementsByClass('Chord'):
    			print(thisChord.pitchedCommonName)
    	except:
    		print("Error : 読み込みエラー")
    	return ()
    
    if __name__ == '__main__':
    	args = sys.argv
    	if 2 <= len(args):
    		if(os.path.exists(args[1])):
    			midiChordAnalysis(args[1])
    		else:
    			print('ERROR : ファイルが存在しません。')
    	else:
    		print('ERROR : 引数がありません。')
```

3. テスト用にmidiを作成(わかりやすくCメジャー)、読み込ませてみる。

```
>py .\midiChordAnalysis.py .\chord.midi
C-major triad
```

おお、見事Cメジャーと言い当てました。

そして俺にっこり
------------

ここまでで作ったものをいい感じに結合する。

```C#
    private void AnalysisBtn_Click(object sender, EventArgs e)
    {
        //MIDIファイルの作成
        if (!makeMidiFile(midiFile))
        {
            MessageBox.Show("MIDIファイルが出力できませんでした。");
            return;
        }

        string Result = String.Empty;

        try
        {
            var pythonResult = new Process
            {
                //pythonExe -> Python.exeのフルパス
                //pythonScript -> midiChordAnalysis.pyの相対パス
                //midiFile -> 作成したmidiファイルの相対パス
                StartInfo = new ProcessStartInfo()
                {
                    FileName = pythonExe,
                    UseShellExecute = false,
                    CreateNoWindow = false,
                    RedirectStandardOutput = true,
                    Arguments = pythonScript + " " + midiFile
                }
            };

            pythonResult.Start();
            StreamReader myStreamReader = pythonResult.StandardOutput;
            Result = myStreamReader.ReadLine();
            pythonResult.WaitForExit();
            pythonResult.Close();
        }
        catch (Exception ex)
        {
            Result = null;
        }

        if (Result != null && !Result.Contains("Error"))
        {
            label2.Text = Result;
        }
        else
        {
            label2.Text = "解析できませんでした。";
            MessageBox.Show("コード解析ができませんでした。");
        }
    }
```

こんな感じにしてみました。

さて実行結果、試しにDメジャーを入力して解析ボタンを押すと…
![Result.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317702/b71f4f17-4481-8e69-db29-36195ca7d786.png)

ちゃんと出ましたね！
なんか複雑なコードも入れてみましょう。

例えばギターの神様こと[ジミ・ヘンドリックス](https://ja.wikipedia.org/wiki/%E3%82%B8%E3%83%9F%E3%83%BB%E3%83%98%E3%83%B3%E3%83%89%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B9)が多用したコードで知られる、
E7(#9) 通称 ジミヘンコードを入力してみます。
![Result02.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317702/a59883cb-573f-f463-a996-b7460498adfc.png)

うーん、よくわからん。
music21というライブラリは音楽全般の解析用ライブラリみたいなので
ギターコードのように簡易な表記にはしてくれないようです…

結果
------------
###コード解析できたが解析結果が読み取れない


Python ScriptのExe化とかもやってみたいと思っていたが間に合わなかったので、
機会があったらまた同じようなことに挑戦してみたいと思います。