ゆんの業務改善ブログ

①生産性向上 ②業務改善 ③自動化 について情報発信しています。VBAプログラムは本当の初心者から他のアプリケーションを呼び出して使う上級者的な使い方まで幅広いレベルで解説していきます。

VBA 上級技 エクセルでピアノを作る

f:id:mutable_yun:20200303230148p:plain
エクセルでピアノの鍵盤を作る方法を解説します。直接的に何かの役に立つわけではなく、プログラミングの練習、もしくは単なる遊びです。しかし、勉強する事と遊びの境目がなくなれば 最強 です。楽しんでやれば挫折しないからです。それでは早速見ていきましょう。

目次

エクセルでピアノを作る事の概要

エクセルでピアノを作る方法についてその流れを押さえ、全体像を把握する事から始めます。その前に前提のお話を少しだけさせて下さい。

前提:APIは上級技だけど難しくない

題名に上級技とありますが、ピアノを作るエクセルのプログラミングが難しいと言うわけではありません。APIというWindowsの機能を借りてきて利用する事が一般にVBAでは「上級」と言われているためにその言い方に合わせました。他のシステムの機能を借りてきて使う事がエクセルの中だけで処理が済むプログラムより高度なので上級と呼ばれているのですが、上級=難しいと言うわけではありません。逆にIfやForなど基礎的な文法市価使っていなくても、複雑な分岐をする必要がある場合などはプログラミングをする事が難しい場合があります。このように、「上級」とされている技術を利用することが難しいとは限らないため、先入観で新しい技術を取り入れるのをためらわないようにしましょう。

エクセルでピアノを作る作業の流れ

エクセルでピアノを作る作業は下のような流れとなります。

  1. エクセルシートに鍵盤の絵を描く
  2. 標準モジュールにメインのプロシジャを挿入する
  3. 標準モジュールの冒頭にAPIの宣言を書く
  4. シートモジュールにプロシジャを挿入する
  5. 標準モジュールを仕上げるクリックした場所に応じた音を出すようにする

一見難しいようでもこのように作業を細分化し、ひとつひとつを着実にこなしていくことで目的を達成する事ができます。

エクセルシートに鍵盤の絵を描く

まず、ピアノの鍵盤となる部分を作成します。分かりやすい様に、シート名はSheet1からkey_boardに変更しました。

次に、行と列のスケールを調整します。私の場合は1行目の行の高さを150、2行目の行の高さを100にしました。そして、E列からAT列までの列の幅を1.5に変更します。これはE列からAT列の部分ピアノの鍵盤にするためです。これで行と列の幅を整える作業は完了です。

次に、黒鍵の部分を作ります。ピアノの鍵盤は白黒白黒白白黒白黒白黒白となっています。黒鍵は1行目に作ります。言葉で説明しても分かりづらいので、自作される方は下の図を真似して作成してみて下さい。

ピアノの鍵盤をエクセルシート上に再現する
エクセルシートに作成したピアノの鍵盤

罫線を引けば鍵盤はほぼ完成です。見栄えを良くするためにセルの灰色枠線を非表示にしています。セルの枠線を非表示にする方法はリボンの上にあるタグから表示を選択して、目盛線のチェックボックスを外せばOKです。

セルの境界の灰色の枠線を外す方法
表示タグを選択して「目盛線」のチェックボックスを外す

標準モジュールにメインのプロシジャを挿入する

今回はイベントプロシジャを使いますが、押された場所に応じた音を鳴らす部分は標準モジュールに記述します。ピアノをもしたエクセルシートを作成するのが今回の目的のため、メインのプロシジャ名はPianoにします。

' これは標準モジュールに書きます
Sub Piano()
    
End Sub

そしてこのPianoプロシジャの中にAPIを呼び出して、クリックされた場所に応じた音を鳴らすプログラムを書いていきます。

標準モジュールの冒頭にAPIの宣言を書く

このプログラムの心臓部分となるPianoプロシジャの中身の作成に入る前に、APIを使う準備を行います。

APIとは、他のアプリケーションの機能を借りてくることです。ここではWindows API、つまりWindowsの音を鳴らす機能を借ります。エクセルには音を鳴らす機能が無いからです*1。そとから機能を借りてくるので、何の機能を借りてくるのかを予め宣言しておく必要があります。そこで、先ほどのイベントプロシジャの上にAPIの宣言を記述します。

'これは標準モジュールのPianoプロシジャの上に書きます。
Declare PtrSafe Function Beep Lib "kernel32" _
    (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long

APIの宣言は冒頭で行いますので、先ほど挿入したPianoプロシジャの上に記述して下さい*2

なお、APIの使い方や必要なAPIの調べ方について詳しく知りたい方はVBA Windows APIを使う手順(調べる→宣言する→使う)で詳しく解説していますので参考にしてみて下さい。

シートモジュールにメインのプロシジャを呼び出すプロシジャを記述する

今回はイベントプロシジャを利用します。イベントとは、ユーザーの入力など外部からの影響をトリガーに実行されるプログラムのことです。今回はシートをクリックされたら、標準モジュールを呼び出すようにします。このようにする理由は、押されたら音を鳴らすというイベントプロシジャにする必要があることと、シートモジュールのようにオブジェクトにくっついているモジュールではAPIの宣言ができないためです。

イベントプロシジャにはシートモジュールなどオブジェクトにくっついたモジュールを利用する
シートモジュールはオブジェクトにくっついているため、既に存在している


このことからシートモジュールはメインのモジュールを呼び出すところだけを担当し、メインの押された場所に応じて音を鳴らす部分は標準モジュールに記述します。

シートモジュールはワークシートに付属したモジュールのため、既に存在します。ここに標準モジュールのPianoプロシジャを呼び出すVBAを記述します。イベントプロシジャを記述するときはモジュール名は自分で書くのではなく、トリガーとなる出来事に応じたプロシジャ名を選択します。


イベントプロシジャが始めてでよく分からない、と言う方はVBA 3分で分かる便利なイベントの使い方 ワークブックにチェック機能をつけるにイベントプロシジャの書き方を具体例を挙げながら解説しているので、参考にして下さい。

ユーザーがシート上でクリックをするとクリックをしたところのセルがActiveとなります。つまり選択した場所が変わります。このため、Worksheet_SelectionChangeを選択します。すると、下記の様になります。

'これはシートモジュールに書きます。
Private Sub Worksheet_SelectionChange(ByVal Target As Range)

End Sub

このPrivate SubとEnd Subの間に標準モジュールに記述した音を流すプログラムを呼び出す記述をします。

'これはシートモジュールに書きます。
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    
    Call Piano(Target.Row, Target.Column)
    
End Sub

押された場所が大事なので、呼び出すPianoプロシジャの所に引数にTarget.Row、Target.Columnを書く事で、クリックされた場所の行番号と列番号をPianoプロシジャに渡します。

音を出すようにする

準備が整いました。ここから実際にシート上のどこをクリックしたかで出す音が変わるプログラムを記述していきます。

音程とヘルツ数を調べる

ドレミのという音の高さはヘルツという数字で表されます。そこで、ドからシまでのそれぞれの音程を調べます。

261.626 :ド
277.183 黒鍵
293.665 :レ
311.127 黒鍵
329.628 :ミ
349.228 :ファ
369.994 黒鍵
391.995 :ソ
415.305 黒鍵
440.000 :ラ
466.164 黒鍵
493.883 :シ

523.251 :ド
554.365 黒鍵
587.330 :レ
622.254 黒鍵
659.255 :ミ
698.456 :ファ
739.989 黒鍵
783.991 :ソ
830.609 黒鍵
880.000 :ラ
932.328 黒鍵
987.767 :シ

2オクターブ分の鍵盤があるので周波数も2オクターブ分調べました。

なお、この記事では音階を調べるために下のページを参考にさせて頂きました。
https://tomari.org/main/java/oto.html

今回使うAPIでは音の高さをヘルツを使って表現します。

とりあえず音が出るようにする

それではメインのPianoプロシジャを作成してみます。まずは音を単に音を鳴らしてみましょう。Beepという音を鳴らすAPIを使うという宣言を既に行っているため、たった一行で音を鳴らすことができます。まずはラの音を1秒流すだけのプログラムを書いてみましょう。

Sub Piano()
    
    Call Beep(440, 1000)
    
End Sub

ラは440Hzなので、第1引数に440を渡しました。Beepは音を流す間の時間をミリ秒で指定します。1秒は1,000ミリ秒なので、1000を第2引数にしました。この状態でどこかをシート状のどこかのセルをクリックすると「ポー」という聴力検査のような音が出ます。APIの利用が完了です。たった一行でとても簡単ですね。冒頭で述べた上級技なのに簡単ということはこういうことです。

メインのプログラムを記述する

それでは次にメインのプログラムを作成していきます。

クリックされてアクティブになったセルの行と列の値を受け取れるようにする
Sub Piano(rw, clm)
    
    Call Beep(440, 1000)
    
End Sub

まずは、クリックされた場所を知るためにイベントプロシジャから呼び出すときに渡された引数を受け取れるようにします。イベントプロシジャではPiano(Target.Row, Target.Column)というように2つの引数を渡しながらPianoプロシジャを呼び出しました。この値をPianoプロシジャ側ではrwとclmという変数で受け取ります。rwはRowで行の意味、clmはColumnで列の意味です。ここは自分がわかりやすい変数名にしておけばよいです。

条件分岐させてクリックされた場所に応じた音が出るようにする

どこを押されたかによって出す音が違うので条件分岐を行うことにします。そして、If文は最大で20個までしかElse Ifが使えません。そこで、IfとSelect Case分を組み合わせて使うことにします。

プログラムがしやすいようにセルの表示をR1C1形式にしておきます。リボンの上のタブのファイルオプション数式R1C1参照形式を使用すると進みます。すると、列がABCではなく、1、2、3と言う表示になります。

f:id:mutable_yun:20200303183508p:plain
R1C1形式表記にしたエクセルシート

ピアノの構成をよく見て条件分岐をどうするか考えます。まず、列は5列目から46列目の間以外は音が鳴らないことが確定です。5~46列目であれば、どこを選択したらド、などと一つ一つ条件分岐していきます。

例えば5列目、6列目ならド。7列目なら、1行目ならドの♯。2行目ならド、と言った具合です。これを地道にプログラミングしていくと下記の通りとなります。

Declare PtrSafe Function Beep Lib "kernel32" _
    (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long


Sub Piano(rw, clm)
    
    If rw <= 2 Then
        Select Case clm
            Case 5, 6
                Call Beep(262, 500)
            Case 7
                If rw = 1 Then
                    Call Beep(277, 500)
                Else
                    Call Beep(262, 500)
                End If
            Case 8
                If rw = 1 Then
                    Call Beep(277, 500)
                Else
                    Call Beep(294, 500)
                End If
            Case 9
               Call Beep(294, 500)
            Case 10
                If rw = 1 Then
                    Call Beep(311, 500)
                Else
                    Call Beep(294, 500)
                End If
            Case 11
                If rw = 1 Then
                    Call Beep(311, 500)
                Else
                    Call Beep(330, 500)
                End If
            Case 12, 13
                Call Beep(330, 500)
            Case 14, 15
                Call Beep(349, 500)
            Case 16
                If rw = 1 Then
                    Call Beep(370, 500)
                Else
                    Call Beep(349, 500)
                End If
            Case 17
                If rw = 1 Then
                    Call Beep(370, 500)
                Else
                    Call Beep(392, 500)
                End If
            Case 18
                Call Beep(392, 500)
            Case 19
                If rw = 1 Then
                    Call Beep(415, 500)
                Else
                    Call Beep(392, 500)
                End If
            Case 20
                If rw = 1 Then
                    Call Beep(415, 500)
                Else
                    Call Beep(440, 500)
                End If
            Case 21
                Call Beep(440, 500)
            Case 22
                If rw = 1 Then
                    Call Beep(466, 500)
                Else
                    Call Beep(440, 500)
                End If
            Case 23
                If rw = 1 Then
                    Call Beep(466, 500)
                Else
                    Call Beep(494, 500)
                End If
            Case 24, 25
                Call Beep(494, 500)
            
            'オクターブ
            Case 26, 27
                Call Beep(262 * 2, 500)
            Case 28
                If rw = 1 Then
                    Call Beep(277 * 2, 500)
                Else
                    Call Beep(262 * 2, 500)
                End If
            Case 29
                If rw = 1 Then
                    Call Beep(277 * 2, 500)
                Else
                    Call Beep(294 * 2, 500)
                End If
            Case 30
               Call Beep(294 * 2, 500)
            Case 31
                If rw = 1 Then
                    Call Beep(311 * 2, 500)
                Else
                    Call Beep(294 * 2, 500)
                End If
            Case 32
                If rw = 1 Then
                    Call Beep(311 * 2, 500)
                Else
                    Call Beep(330 * 2, 500)
                End If
            Case 33, 34
                Call Beep(330 * 2, 500)
            Case 35, 36
                Call Beep(349 * 2, 500)
            Case 37
                If rw = 1 Then
                    Call Beep(370 * 2, 500)
                Else
                    Call Beep(349 * 2, 500)
                End If
            Case 38
                If rw = 1 Then
                    Call Beep(370 * 2, 500)
                Else
                    Call Beep(392 * 2, 500)
                End If
            Case 39
                Call Beep(392 * 2, 500)
            Case 40
                If rw = 1 Then
                    Call Beep(415 * 2, 500)
                Else
                    Call Beep(392 * 2, 500)
                End If
            Case 41
                If rw = 1 Then
                    Call Beep(415 * 2, 500)
                Else
                    Call Beep(440 * 2, 500)
                End If
            Case 42
                Call Beep(440 * 2, 500)
            Case 43
                If rw = 1 Then
                    Call Beep(466 * 2, 500)
                Else
                    Call Beep(440 * 2, 500)
                End If
            Case 44
                If rw = 1 Then
                    Call Beep(466 * 2, 500)
                Else
                    Call Beep(494 * 2, 500)
                End If
            Case 45, 46
                Call Beep(494 * 2, 500)
        End Select
    End If
        
End Sub

プログラムはかなり長くなってしまいましたが、どこを押されたかをひたすら分岐しているだけなので、命令としては1行しか実行されません。オクターブはヘルツ数を2倍しています。反省点としては音の長さが決め打ちな所です。クリックした瞬間から0.5秒を機械的にならしています。別のAPIを使えばマウスのクリックを離した瞬間*3に音が途切れるようにするなどが考えられます。また、複数の音を同時にならすこともできません。

APIに慣れ親しむ目的で紹介しましたので、このあたりでご容赦頂ければと思います。

VBA 上級技 エクセルでピアノを作る方法のまとめ

この記事の内容をまとめます。

  • VBAによるプログラミングでAPIは上級向けとされているが、難しくはない
  • APIは宣言さえ分かればあとは呼び出すだけで使える
  • ピアノを作ろうとすると分岐が沢山必要である
  • ピアノの鍵盤をセルで代用しようとすると、イベントプロシジャが必要となる

勉強とは思わずに、遊びでこのようなプログラムを作ってみるのも良いと思います。

<関連記事>

*1:文字を読み上げる機能はあります

*2:Option Explicitを挿入している方はその下です

*3:マウスアップと言います