Delphi で micro:bit の音階コードを生成してみた (プロトタイプ編)

micro:bit で、自分の好きな曲を演奏するには
![mb04.png](https://qiita-image-store.s3.amazonaws.com/0/164551/62fc3d94-e3e1-cade-d3b6-5d4c0545df84.png)
な感じにコツコツと楽譜を入力しないとならぬ
拍数なども試し試しで入力するのである

個人的には、鍵盤をつかって演奏したら、演奏したイメージでコードを生成して欲しいというワガママがむくむくと
じゃ Delphi で作ってみるべと思うのである

# 下調べ
どのようなコードが生成されているか、入力すべき値などを確認する

## 生成するべき JavaScript のコードを確認する
上記の scrach ベースのコードを JavaScript で確認すると

```javascript
   music.setTempo(120)  --> テンポの設定 120bpm
   music.playTone(262, music.beat(BeatFraction.Half)) --> 真ん中のド 1/2拍
   music.playTone(330, music.beat(BeatFraction.Half)) --> ミ 
   music.playTone(349, music.beat(BeatFraction.Half)) --> ファ
   music.playTone(392, music.beat(BeatFraction.Whole)) --> ソ 1拍
   music.rest(music.beat(BeatFraction.Quater))  --> 休符 1/4拍
```

となる

## 音符のヘルツを確認する

playtone で指定されるのは音符のヘルツ(四捨五入)の値なので、ヘルツの値を確認した
C0 は 低いド、C1 は真ん中のド、C2 は高いドを表しています。

| 音階 | C0 | C0# | D0 | D0# |E0 | F0 | F0# | G0 | G0# | A0 | A0# | B0 |
|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
|ヘルツ|131|139|147|165|156|175|185|196|208|220|233|247|


| 音階 | C1 | C1# | D1 | D1# |E1 | F1 | F1# | G1 | G1# | A1 | A1# | B1 |
|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
|ヘルツ|262|277|294|311|330|349|370|392|415|440|466|494|


| 音階 | C2 | C2# | D2 | D2# |E2 | F2 | F2# | G2 | G2# | A2 | A2# | B2 |
|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
|ヘルツ|523|554|587|622|659|698|740|784|831|880|932|988|

## 拍数の記述を確認する

BeatFreaction で指定できる拍数は 1, 1/2, 1/4, 1/8, 1/16, 2, 4 でそれぞれの記述を確認した

| 拍数 | 記述 | 
|------|------|
| 1 | Whole |
| 1/2 | Half | 
| 1/4 | Quarter |
| 1/8 | Eighth | 
| 1/16 | Sixteenth |
| 2 | Double | 
| 4 | Breve |

## テンポと拍数と秒数の関係を確かめる
60bpm のときの 1 泊は 1 秒
120bpm のときの 1 拍は 0.5 秒
指定可能なテンポの値は 4bpm から 400bpm の間 (デフォルト 120bpm)

となると ミリ秒で差を出して 1 秒 =1000 ミリ秒なので 1000 で割って、基本の 60bpm をかけて、設定されている bpm で割るで計算あってるかな？
1000 (1秒) / 1000 * 60 だと 60 になるから 60 bpm のときは 1 だし、120 bpm は 0.5 になるから OK (のはず)

# Delphi で作ってみる
といっても唐突には作れないので、仕様を決める

## 作るアプリケーションの仕様を考える

- 初期バージョンなので、曲の途中ではテンポを変えない
- テンポは最初に決定する
- 鍵盤は１オクターブ分だけ用意する
- オクターブの高低はスイッチ的な要素で決定する
- 鍵盤を押している間の時間で拍数を算出する
- 鍵盤を離して次の鍵盤を押すまでの間が 1/16 拍以上であれば休符を入れる
- 和音は考えない
- 生成したコードは画面上でコピーして micro:bit のエディタにペーストができればいい（自分しか使わないから）
 


## 実際に作ってみた
今回はプロトタイプなので、あくまで鍵盤のキーと拍数がそこそこ合っていて、文字列が生成できればよい
今回はプロトタイプなので休符とオクターブの高低は考えていない
今回はベタの if 文で判定した
ボタンの操作は同じなので Button1MouseUp / Button1MouseDown にすべてのボタンの処理を集約し、ボタンの種類は TButton(sender).Text; で判断(Cとか C#とかの文字列が入ります)

### できあがったプロトタイプ画面
![music01.png](https://qiita-image-store.s3.amazonaws.com/0/164551/b120a6f2-3e5a-26bd-4a94-08e364817f81.png)

### コードはこちら

```pascal
// プロトタイプなのでエラーなどは全く考慮していないベタな感じで書いてます

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Edit,
  FMX.EditBox, FMX.NumberBox, FMX.StdCtrls, FMX.Controls.Presentation,
  FMX.ScrollBox, FMX.Memo, FMX.Layouts, System.Math, System.DateUtils;

type
  TForm1 = class(TForm)
    Layout1: TLayout;
    Layout2: TLayout;
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    Button10: TButton;
    Button11: TButton;
    Button12: TButton;
    Label1: TLabel;
    NumberBox1: TNumberBox;
    procedure NumberBox1Change(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure Button1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure Button1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
  private
    { private 宣言 }
    const setTempo = 'music.setTempo(';
    const playTone = 'music.playTone(';
    const rest = 'music.rest(';
    const beat = 'music.beat(';

    const whole = 'BeatFraction.Whole';
    const half = 'BeatFraction.Half';
    const quarter = 'BeatFraction.Quarter';
    const Eighth = 'BeatFraction.Eighth';
    const Sixteenth = 'BeatFraction.Sixteenth';
    const B_Double = 'BeatFraction.Double';
    const Breve = 'BeatFraction.Breve';

    const e_1 = ')';
    const e_2 = '))';

    procedure generate_setTempo;
    procedure generate_playTone;
    procedure generate_rest;

  public
    { public 宣言 }
    timeCount: Double;
    StartTime, EndTime: TDateTime;
    Kenban, Hz, Haku: string;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{ TForm1 }

procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  StartTime := Now;
  EndTime := 0;
  Kenban := TButton(sender).Text;
end;

procedure TForm1.Button1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  EndTime := Now;
  timeCount := ((MilliSecondsBetween(EndTime, StartTime) / 1000) * 60) / NumberBox1.Value;
  generate_playTone;
  StartTime := 0;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  generate_setTempo;
end;

procedure TForm1.generate_setTempo;
var
  javaStrings: string;
begin
  javaStrings := setTempo + NumberBox1.Text + e_1;
  Memo1.Lines.Add(javaStrings);
end;

procedure TForm1.generate_playTone;
var
  javaString: string;
  cnt: double;
begin
  if Kenban = 'C' then Hz := '262';
  if Kenban = 'C#'then Hz := '277';
  if Kenban = 'D' then HZ := '294';
  if Kenban = 'D#'then HZ := '311';
  if Kenban = 'E' then Hz := '330';
  if Kenban = 'F' then Hz := '349';
  if Kenban = 'F#'then Hz := '370';
  if Kenban = 'G' then Hz := '392';
  if Kenban = 'G#'then Hz := '415';
  if Kenban = 'A' then Hz := '440';
  if Kenban = 'A#'then Hz := '466';
  if Kenban = 'B' then Hz := '494';

  cnt := Roundto(timeCount, -2);

  if cnt <= (1/16) then haku := Sixteenth;
  if (cnt <= (1/8)) and (cnt > (1/16)) then haku := Eighth;
  if (cnt <= (1/4)) and (cnt > (1/8)) then haku := quarter;
  if (cnt <= (1/2)) and (cnt > (1/4)) then haku := half;
  if (cnt <= 1) and (cnt > (1/2)) then haku := whole;
  if (cnt <= 2) and (cnt > 1) then haku := B_Double;
  if cnt > 2 then haku := Breve;

  javaString := playTone + Hz + ', ' + beat + haku + e_2;
  Memo1.Lines.Add(javaString);
end;

procedure TForm1.generate_rest;
var
  javaString: string;
begin
  javaString := rest + beat + half + e_2;
  Memo1.Lines.Add(javaString)
end;

procedure TForm1.NumberBox1Change(Sender: TObject);
begin
  generate_setTempo;
end;

end.

```

## あとがき
プロトタイプだけど、micro:bit側での操作は恐ろしく楽になった
もうちょっとスマートに書くのと、鍵盤をそれらしく表現して完成させるのは次回

しかし、こんな拡張ボードがあったのか....これは直接演奏するときですが、音源を作りたいのよね
https://www.switch-science.com/catalog/4030/