Delphi で micro:bit の音階コードを生成してみた ( オクターブ処理編 )

[プロトタイプ編](https://qiita.com/CYonezawa/items/4b07b21e924f5a86d5ea) で、作ったアプリケーション内のコードですが

```pascal
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';

```

の箇所があまりにもベタなので、配列を使って書き直すのと、ついでにオクターブに関する処理も加えてみた

## 画面の変更点

- 鍵盤の両端にオクターブを操作するためのボタンを追加
- 現在どの音程のところにいるかを表示するボックスを追加（0 が低音、1 が通常、2 が高音)

![music02.png](https://qiita-image-store.s3.amazonaws.com/0/164551/36707693-64d3-f35b-c5a9-659f35480fa1.png)


## コードの変更点

- 音階の配列と、ヘルツの配列を設定して、Form.Create のタイミングで初期化するようにした
- 音階のキーの配列の添え字と、ヘルツの配列の添え字を対応させておく
- for ループで配列内を操作して合致した添え字のヘルツを取り出すという仕組みに変更
- 音階の配列は 3 種類用意して現在の音程で切り替えている

配列と for ループを使うと、ベタで書いていた箇所がスッキリ！

```pascal
  for I := 0 to 11 do begin
    if Kenban = octave[i] then Hz := Hz1[i];
  end;
```

## オクターブ処理編の全体のコードはこちら

```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;   // C-ド
    Button2: TButton;   // C# - ド#
    Button3: TButton;   // D - レ
    Button4: TButton;   // D# - レ#
    Button5: TButton;   // E - ミ
    Button6: TButton;   // F - ファ
    Button7: TButton;   // F# - ファ#
    Button8: TButton;   // G - ソ
    Button9: TButton;   // G# - ソ#
    Button10: TButton;  // A - ラ
    Button11: TButton;  // A# - ラ#
    Button12: TButton;  // B - シ 
    Label1: TLabel;
    NumberBox1: TNumberBox;  // Tempo
    Button13: TButton;  // Low
    Button14: TButton;  // High
    NumberBox2: TNumberBox;  // Octave
    Label2: TLabel;
    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);
    procedure FormCreate(Sender: TObject);
    procedure Button13Click(Sender: TObject);
    procedure Button14Click(Sender: TObject);
  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;
    Octave_sw: Integer;
    Hz0, Hz1, Hz2: array of string;
    octave: array of string;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{ TForm1 }

procedure TForm1.Button13Click(Sender: TObject);
begin
  Octave_sw := Octave_sw -1;
  if Octave_sw <= 0 then Octave_sw := 0; // 0 以下は 0
  NumberBox2.Value := Octave_sw;
end;


procedure TForm1.Button14Click(Sender: TObject);
begin
  Octave_sw := Octave_sw + 1;
  if Octave_sw >= 2 then Octave_sw := 2;  // 2 以上は 2
  NumberBox2.Value := Octave_sw;
end;

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.FormCreate(Sender: TObject);
begin
// 初期値設定
  Octave_sw := 1;

  SetLength(Hz0, 12);
  SetLength(Hz1, 12);
  SetLength(Hz2, 12);
  SetLength(octave, 12);

  Hz0 := ['131','139','147','165','156','175','185','196','208','220','233','247'];
  Hz1 := ['262','277','294','311','330','349','370','392','415','440','466','494'];
  Hz2 := ['523','554','587','622','659','698','740','784','831','880','932','988'];
  octave := ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  generate_setTempo;  // 最初に tempo(120) の文字列を生成
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;
  I: Integer;
begin
  if Octave_sw = 0 then
  begin
    for I := 0 to 11 do begin
      if Kenban = octave[i] then Hz := Hz0[i];
    end;
  end else if Octave_sw = 1 then
  begin
    for I := 0 to 11 do begin
      if Kenban = octave[i] then Hz := Hz1[i];
    end;
  end else begin
    for I := 0 to 11 do begin
      if Kenban = octave[i] then Hz := Hz2[i];
    end;
  end;

  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.

```

## あとがき
これで結構個人的には十分だったりします
単音ですが 1 曲分のコード書きがさくさくできます
そして、残りは休符のロジックの実装です