2022-05-07 sound > 音階を指定してMIDIの音として鳴らす (C++ Builder Alexandria実装)

### 動作環境

- Windows 10 Pro
- C++ Builder Alexandria 
- VCLプロジェクト

### 概要

マウスクリックで音符を指定して、MIDI音として鳴らす。

### 参考

- https://teratail.com/questions/123462
- http://www.aislab.org/multimedia_exp/sample4.1.html

### 動作例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32870/63d99c53-0357-bf2f-5a2a-0675a09b1b7f.png)

### bg.bmp

Qiitaにbmpファイルを貼ろうとしてエラーになるので説明だけ。
600 x 390のサイズで画像を用意した。
各音階のY方向は30としている。
上位が「ド」の音に対応するように鍵盤を描画している。

### 実装

```Main.h
//---------------------------------------------------------------------------

#ifndef MainH
#define MainH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ColorGrd.hpp>
#include <Vcl.ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TFormMain : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TButton *B_play;
	void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y);
	void __fastcall B_playClick(TObject *Sender);
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TFormMain(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TFormMain *FormMain;
//---------------------------------------------------------------------------
#endif
```

```Main.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <MMSystem.h> // for MIDI
#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TFormMain *FormMain;
//---------------------------------------------------------------------------

// 音符シート
#define XMAP (600)
#define YMAP (390)

// 音符サイズ
#define XDIV (30)
#define YDIV (30)

// 音符の数
#define XSIZE (XMAP / XDIV)
#define YSIZE (YMAP / YDIV)

// 音符
static bool s_sound[YSIZE][XSIZE] = {0};

// 音符のスプライト
#define XSPR (20) // スプライトサイズ
#define YSPR (10) // スプライトサイズ


__fastcall TFormMain::TFormMain(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
		  int X, int Y)
{
	if (X > XMAP || Y > YMAP) {
		return;
	}

	Graphics::TBitmap *Bitmap = new Graphics::TBitmap();
	Bitmap->LoadFromFile("bg.bmp");
	FormMain->Canvas->Brush->Bitmap = Bitmap;
	FormMain->Canvas->FillRect( Rect(0,0,XMAP,YMAP) );

	int posx = X / XDIV;
	int posy = Y / YDIV;
	s_sound[posy][posx] = !s_sound[posy][posx];

	for(int idy=0; idy < YSIZE; idy++) {
		for(int idx=0; idx < XSIZE; idx++) {
			if (s_sound[idy][idx]) {
				int xst = idx * XDIV + XDIV / 2 - XSPR / 2;
				int yst = idy * YDIV + YDIV / 2 - YSPR / 2;
				FormMain->Canvas->Brush->Color = clRed;
				FormMain->Canvas->FillRect( Rect(xst,yst,xst+XSPR,yst+YSPR) );
			}
		}
	}
}
//---------------------------------------------------------------------------
// 和音を出す
// https://teratail.com/questions/123462

HMIDIOUT ghMidiOut;

void noteOn(unsigned iNote, unsigned iVelocity)
{
	unsigned packdata = 0x90 | (iNote << 8) | (iVelocity << 16);
	midiOutShortMsg(ghMidiOut, packdata);
}

void noteOff(unsigned iNote)
{
	unsigned packdata = 0x80 | (iNote << 8);
	midiOutShortMsg(ghMidiOut, packdata);
}

void __fastcall TFormMain::B_playClick(TObject *Sender)
{
//	// A. 単音
//
//	Beep(1046.502, 250); // do
//	Beep(1174.659, 250); // re
//	Beep(1318.510, 250); // mi
//	Beep(1396.913, 250); // fa
//	Beep(1567.982 ,250); // so
//	Beep(1760.000, 250); // ra
//	Beep(1975.533, 250); // si
//	Beep(2093.005, 250); // do

	// B. 和音可能

    // https://teratail.com/questions/123462
	// http://www.aislab.org/multimedia_exp/sample4.1.html

	MMRESULT mmres = midiOutOpen(&ghMidiOut, MIDI_MAPPER, NULL, NULL, CALLBACK_NULL);

	if(mmres != MMSYSERR_NOERROR) {
		fprintf(stderr, "MIDIが利用できません。\n");

		return;
	}

	static int solfa[ ] = { 60, 62, 64, 65, 67,   69, 71, 72, 74, 76,  77, 79, 81  };  // do re mi fa so la si do ...
	int size_solfa = sizeof(solfa) / sizeof(solfa[0]);

	Sleep(300);
	for(int idx=0; idx < XSIZE; idx++) {
		// 1. ON
		for(int idy=0; idy < YSIZE; idy++) {
			if (idy < size_solfa) {
				if (s_sound[idy][idx]) {
					noteOn(solfa[idy], 127);
				}
			}
		}
		// 2. wait
		Sleep(300);
		// 3. OFF
		for(int idy=0; idy < YSIZE; idy++) {
			if (idy < size_solfa) {
				if (s_sound[idy][idx]) {
					noteOff(solfa[idy]);
				}
			}
		}
	}
	Sleep(300);

	midiOutClose(ghMidiOut);
}
//---------------------------------------------------------------------------
```

### 備考

Beep()を使うと和音ができないため、MIDIの処理を実装するようにした。