Golangで演奏する電子オルゴール

以前、[『PowerShellで演奏する電子オルゴール』](https://qiita.com/gx3n-inue/items/8ca1d987d58235e7f434)や[『Python3で演奏する電子オルゴール』](https://qiita.com/gx3n-inue/items/d7ae2d4962c41af62fc2)の記事を書きましたが、同様の処理をGolangに移植してみました。

GitHubにて、ソースコードおよびサンプルの楽譜データも公開しています。 

https://github.com/NobuyukiInoue/Go_PlayBox

MIDI関連のWin32APIを利用し、Golangで楽譜データを演奏させてみます。 
音階とノートナンバーの変換表や、楽譜データのフォーマットは、PowerShell版やPython3版と同じです。

#音階データとノート番号の変換表

楽譜データを入力する際に、ドレミ...もしくはdo,re,mi,...というように 音階で入力できるように、音階データとノート番号の変換表を作成しておきます。

```text:note-number.txt
// 全角カタカナ表記
ド4=3c
ド#4=3d
レ4=3e
レ#4=3f
ミ4=40
フ4=41
フ#4=42
ソ4=43
ソ#4=44
ラ4=45
ラ#4=46
...
(長いので途中省略）
...
...
re#8=6f
mi8=70
fa8=71
fa#8=72
so8=73
so#8=74
ra8=75
ra#8=76
si8=77
```

#楽譜データの作成

次に、演奏させたい曲の楽譜データを下記の書式で並べていきます。

音階およびオクターブ番号=演奏時間(ms)
『こぎつねこんこん』だとこんな感じになります。 ちなみに、同じ長さの音符であれば、和音も鳴らすことができます。

```text:PB_kitune.txt
ド4,ド5 = 250
レ4 = 250
ミ4 = 250
フ4 = 250
ソ4,ソ5 = 500
ソ4,ソ5 = 500

ラ4 = 250
フ4 = 250
ド5 = 250
ラ4 = 250
ソ4,ソ5 = 1000

ラ4 = 250
フ4 = 250
ド5 = 250
ラ4 = 250
ソ4,ソ5 = 1000

ソ4 = 250
フ4 = 250
フ4 = 250
フ4 = 250

フ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250
レ4 = 250
ミ4 = 250
レ4 = 250
ド4 = 250
ミ4 = 250
ソ4,ソ5 = 500

ソ4 = 250
フ4 = 250
フ4 = 250
フ4 = 250

フ4 = 250
ミ4 = 250
ミ4 = 250
ミ4 = 250

ミ4 = 250
レ4 = 250
レ4 = 250
ミ4 = 250

ド4,ド5 = 1000
```

#MIDI関連Win32API呼び出し処理

MIDI関連のWin32APIを呼び出す処理は、別のパッケージとしてまとめておきます。

```apimidi/apimidi.go
package apimidi

import (
	"fmt"
	"syscall"
	"time"
	"unsafe"
)

// MyMIDI ... MyMIDI struct
type MyMIDI struct {
	initData   int
	MIDIMAPPER int
	h          *uint
	dll        *syscall.DLL
}

// PrintInfo ... Print struct MyMIDI members information.
func (pm *MyMIDI) PrintInfo() {
	fmt.Printf("addr = %x, initData = %x\n", &pm.initData, pm.initData)
	fmt.Printf("addr = %x, MIDIMAPPER = %x\n", &pm.MIDIMAPPER, pm.MIDIMAPPER)
	fmt.Printf("addr = %x, h = %x\n", &pm.h, pm.h)
}

// Init ... MIDI Init
func (pm *MyMIDI) Init(initData int) {
	dll, err := syscall.LoadDLL("winmm.dll")
	if err != nil {
		panic(err)
	}

	pm.dll = dll
	pm.MIDIMAPPER = -1
	pm.initData = initData

	proc, err := pm.dll.FindProc("midiOutOpen")
	if err != nil {
		panic(err)
	}

	proc.Call(uintptr(unsafe.Pointer(&pm.h)), uintptr(pm.MIDIMAPPER), uintptr(0), uintptr(0), uintptr(0))

	proc, err = pm.dll.FindProc("midiOutShortMsg")
	if err != nil {
		panic(err)
	}

	proc.Call(uintptr(unsafe.Pointer(pm.h)), uintptr(pm.initData))
}

// Out ... MIDI Output with time.Sleep
func (pm *MyMIDI) Out(outData int, length time.Duration) {
	proc, err := pm.dll.FindProc("midiOutShortMsg")
	if err != nil {
		panic(err)
	}

	proc.Call(uintptr(unsafe.Pointer(pm.h)), uintptr(outData))
	time.Sleep(length * time.Millisecond)
}

// OutOnly ... MIDI Output without timeSleep
func (pm *MyMIDI) OutOnly(outData int) {
	proc, err := pm.dll.FindProc("midiOutShortMsg")
	if err != nil {
		panic(err)
	}

	proc.Call(uintptr(unsafe.Pointer(pm.h)), uintptr(outData))
}

// Close ... MIDI Close
func (pm *MyMIDI) Close() {
	proc, err := pm.dll.FindProc("midiOutReset")
	if err != nil {
		panic(err)
	}

	proc.Call(uintptr(unsafe.Pointer(pm.h)))
}
```

#メインプログラム

###
メインプログラム側の処理です。

起動時の引数をチェックし、ノート番号の定義ファイルおよび楽譜ファイルを読み込んだあと、
指定された長さで音を鳴らしていきます。

```Go:Go_PlayBox.go
package main

import "./apimidi"

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
)

/* Go text processing suport(SJIS)
https://github.com/golang/text

** Download/Install
The easiest way to install is to run go get -u golang.org/x/text.
You can also manually git clone the repository to $GOPATH/src/golang.org/x/text.
*/

// ScaleDefs ... ScaleDefs struct
type ScaleDefs struct {
	scale string
	note  string
}

// PlayData ... struct PlayData
type PlayData struct {
	scale  string
	note   string
	length int
}

// Load the scale definition file.
func loadDefFile(filename string) []ScaleDefs {
	fp, err := os.Open(filename)
	if os.IsNotExist(err) {
		fmt.Printf("%s not found.", filename)
		return nil
	}

	// File Open
	var defs []ScaleDefs
	scanner := bufio.NewScanner(transform.NewReader(fp, japanese.ShiftJIS.NewDecoder()))
	for scanner.Scan() {
		tempStr := scanner.Text()

		pos := strings.Index(tempStr, "//")
		if pos >= 0 {
			tempStr = tempStr[:pos]
		}

		tempStr = strings.Replace(tempStr, " ", "", -1)
		tempStr = strings.Replace(tempStr, "\t", "", -1)
		flds := strings.Split(tempStr, "=")

		if tempStr != "" && len(flds) >= 2 {
			currentDef := new(ScaleDefs)
			currentDef.scale, currentDef.note = flds[0], flds[1]
			defs = append(defs, *currentDef)
		}
	}

	return defs
}

// Load the score file.
func loadPlayFile(filename string) []PlayData {
	fp, err := os.Open(filename)
	if os.IsNotExist(err) {
		fmt.Printf("%s not found.", filename)
		return nil
	}

	// File Open
	var pData []PlayData
	scanner := bufio.NewScanner(transform.NewReader(fp, japanese.ShiftJIS.NewDecoder()))
	for scanner.Scan() {
		tempStr := scanner.Text()

		pos := strings.Index(tempStr, "//")
		if pos >= 0 {
			tempStr = tempStr[:pos]
		}

		tempStr = strings.Replace(tempStr, " ", "", -1)
		tempStr = strings.Replace(tempStr, "\t", "", -1)
		flds := strings.Split(tempStr, "=")

		if tempStr != "" && len(flds) >= 2 {
			currentData := new(PlayData)
			currentData.scale = flds[0]
			currentData.note = ""
			currentData.length, err = strconv.Atoi(flds[1])

			if err != nil {
				fmt.Printf("%s Atoi() error!!", flds[1])
			}

			pData = append(pData, *currentData)
		}
	}

	return pData
}

// Search the musical scale character string and set the note number.
func replaceScale2Freq(defs *[]ScaleDefs, pData *[]PlayData) {
	for i := 0; i < len(*pData); i++ {
		scale := strings.Split((*pData)[i].scale, ",")
		for _, temp := range scale {
			for _, currentLen := range *defs {
				if temp == currentLen.scale {
					if (*pData)[i].note == "" {
						(*pData)[i].note = currentLen.note
					} else {
						(*pData)[i].note += "," + currentLen.note
					}
					break
				}
			}
		}
	}
}

func fileExists(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("Usage)\n" +
			"go run " + os.Args[0] + " musicDataFile <timbre>\n")
		return
	}

	noteNumberFile := "./note-number.dat"
	if fileExists(noteNumberFile) == false {
		fmt.Printf("%s not found.", noteNumberFile)
		return
	}

	if fileExists(os.Args[1]) == false {
		fmt.Printf("%s not found.", os.Args[1])
		return
	}

	var timbre int
	if len(os.Args) >= 3 {
		readval, err := strconv.Atoi(os.Args[2])
		if err != nil {
			fmt.Printf("%s ... timbre is a not integer!!", os.Args[2])
			return
		}
		timbre = readval
	} else {
		timbre = 1
	}

	// Load the scale definition file.
	defs := loadDefFile(noteNumberFile)

	// Load PlayData file.
	pData := loadPlayFile(os.Args[1])

	// Set note number.
	replaceScale2Freq(&defs, &pData)

	initData := timbre*256 + 0xc0

	// Set Integer Size.
	const intSize = 32 << (^uint(0) >> 63)

	fmt.Printf("intSize = %d, initData = 0x%04x\n", intSize, initData)

	// Initialize the MyMIDI struct and functions
	pm := new(apimidi.MyMIDI)
	pm.Init(initData)

	fmt.Printf("Load Done. Play start!!\n")

	for i, currentpData := range pData {
		if currentpData.note != "" {
			fmt.Printf("[%d] = %s( %s ), %d [ms]\n", i, currentpData.scale, currentpData.note, currentpData.length)
			cnote := strings.Split(currentpData.note, ",")

			for _, data := range cnote {
				// Press the keyboad.
				playOn := "0x7f" + data + "90"
				playData, err := strconv.ParseInt(playOn, 0, intSize)
				if err != nil {

				}

				pm.OutOnly(int(playData))
			}

			// Keep pressed.
			time.Sleep(time.Duration(currentpData.length) * time.Millisecond)

			for _, data := range cnote {
				// Release the keyboad.
				playOff := "0x7f" + data + "80"
				playData, err := strconv.ParseInt(playOff, 0, intSize)
				if err != nil {

				}

				pm.OutOnly(int(playData))
			}
		} else {
			// Rest.
			fmt.Printf("[%d] = rest, %d [ms]\n", i, currentpData.length)
			time.Sleep(time.Duration(currentpData.length) * time.Millisecond)
		}
	}

	pm.Close()
	fmt.Println()
}
```

#演奏
では早速、演奏してみましょう。 下記の書式でGo_PlayBox.goを実行します。

```text:
D:\Go_PlayBox> go run Go_PlayBox.go
Usage)
go run (exeファイルのpath)\Go_PlayBox.exe musicDataFile <timbre>
```

timbreはMIDIの音色（楽器）の指定です。1～127の数値で指定してください。 
以下は、さきほどの『ごぎつねこんこん』(PB_kitune.txt)をハーモニカ(23)で演奏する例です。

```text:
D:\Go_PlayBox> go run Go_PlayBox.go .\PB_kitune.txt 23
intSize = 64, initData = 0x17c0
Load Done. Play Start!!
[0] =  ド4,ド5 ( 3c,48 ),  250 [ms]
[1] =  レ4 ( 3e ),  250 [ms]
[2] =  ミ4 ( 40 ),  250 [ms]
[3] =  フ4 ( 41 ),  250 [ms]
[4] =  ソ4,ソ5 ( 43,4f ),  500 [ms]
[5] =  ソ4,ソ5 ( 43,4f ),  500 [ms]
[6] =  ラ4 ( 45 ),  250 [ms]
[7] =  フ4 ( 41 ),  250 [ms]
[8] =  ド5 ( 48 ),  250 [ms]
[9] =  ラ4 ( 45 ),  250 [ms]
[10] =  ソ4,ソ5 ( 43,4f ),  1000 [ms]
[11] =  ラ4 ( 45 ),  250 [ms]
[12] =  フ4 ( 41 ),  250 [ms]
[13] =  ド5 ( 48 ),  250 [ms]
[14] =  ラ4 ( 45 ),  250 [ms]
[15] =  ソ4,ソ5 ( 43,4f ),  1000 [ms]
[16] =  ソ4 ( 43 ),  250 [ms]
[17] =  フ4 ( 41 ),  250 [ms]
[18] =  フ4 ( 41 ),  250 [ms]
[19] =  フ4 ( 41 ),  250 [ms]
[20] =  フ4 ( 41 ),  250 [ms]
[21] =  ミ4 ( 40 ),  250 [ms]
[22] =  ミ4 ( 40 ),  250 [ms]
[23] =  ミ4 ( 40 ),  250 [ms]
[24] =  ミ4 ( 40 ),  250 [ms]
[25] =  レ4 ( 3e ),  250 [ms]
[26] =  ミ4 ( 40 ),  250 [ms]
[27] =  レ4 ( 3e ),  250 [ms]
[28] =  ド4 ( 3c ),  250 [ms]
[29] =  ミ4 ( 40 ),  250 [ms]
[30] =  ソ4,ソ5 ( 43,4f ),  500 [ms]
[31] =  ソ4 ( 43 ),  250 [ms]
[32] =  フ4 ( 41 ),  250 [ms]
[33] =  フ4 ( 41 ),  250 [ms]
[34] =  フ4 ( 41 ),  250 [ms]
[35] =  フ4 ( 41 ),  250 [ms]
[36] =  ミ4 ( 40 ),  250 [ms]
[37] =  ミ4 ( 40 ),  250 [ms]
[38] =  ミ4 ( 40 ),  250 [ms]
[39] =  ミ4 ( 40 ),  250 [ms]
[40] =  レ4 ( 3e ),  250 [ms]
[41] =  レ4 ( 3e ),  250 [ms]
[42] =  ミ4 ( 40 ),  250 [ms]
[43] =  ド4,ド5 ( 3c,48 ),  1000 [ms]


D:\Go_PlayBox>
```