category name  »  page title date

メロディーを奏でる

時報      TimeSignal()

ポチッと音を出す」で使った BeepAPI を用いて、こんなクラスを作ってみました。別にクラスにすることもないのですが、後で音階を作るのに便利なので。

クラスモジュール  SoundClass

Public Freq As Long
    'Freq は、オクターブが5の場合を基準にした周波数
    'Ring で octave を4とすると、Freq の1オクターブ下の音が出る
       '                6とすると、1オクターブ上
'--- オクターブ、継続時間、その後の休止時間を指定して音を出す
Public Sub Ring(octave, contTime, restTime)
      BeepAPI Freq * 2 ^ (octave - 5), contTime
      Application.Wait [now()] + restTime / 86400000
End Sub

このクラスを使って、こんな音を出してみました。

標準モジュール         TimeSignal()

#If Win64 Then
    Public Declare PtrSafe Function BeepAPI Lib "kernel32" Alias "Beep" _
    (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
#Else
    public Declare Function BeepAPI Lib "kernel32"  Alias "Beep" _
    (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
#End If

'----- 時報
Public Sub TimeSignal()
Dim n
      With New SoundClass
            .Freq = 440
            For n = 1 To 3
                  .Ring 5,100, 900
            Next
            .Ring 6,1000, 0
      End With
End Sub

TimeSignal を実行すると、440Hz 0.1sec が 0.9sec の無音を挟んで3回鳴った後、880Hz が 1sec 鳴ります。NHK の時報の真似。実際はこの後に 2sec のリリース(減衰)があるのですが、音量の調整のしかたがよくわからないので、これでよしとしました。どなたか音量減衰方法をわかる方がおられれば、ぜひご教示ください。

歓喜の歌      SingSong()

今度は、メロディーらしきものを発声できないかとやってみたのが、次の SingSong() です。上の標準モジュールの最初にあった API を呼ぶための宣言は、どこかに書いてあるものとして省略。

標準モジュール      SingSong()

'----- 1オクターブ分の音名を列挙
Private Enum Note
      c
      cis
      d
      es
      e
      f
      fis
      g
      gis
      a
      b
      h
End Enum
Private notes(11) As SoundClass
'----- メトロノームのビート数
Private bpm As Long

'----- 各音名の周波数を平均律で設定
Public Sub CalcNote(pitch)
Dim basePitch, i
      basePitch = pitch * 2 ^ (3 / 12)    'C5 の周波数に換算
      For i = 0 To 11
            Set notes(i) = New SoundClass
            notes(i).Freq = basePitch * 2 ^ (i / 12)
      Next
End Sub

'----- 演奏
Public Sub SingSong( _
    Optional tuningPitch = 440, Optional tempo = 120 _
    )

      CalcNote tuningPitch
      bpm = tempo
      '--- 開始
      xx c, 5, 2, 1                 'C5 を 2拍分目一杯鳴らす
      xx d, 5, 1, 1
      xx e, 5, 1, 0.8            'E5 を 1拍分の80%鳴らしてちょっと休む
      xx e, 5, 1, 1
      xx d, 5, 1, 1
      xx c, 5, 1, 1
      xx g, 4, 1, 0.8
      xx e, 4, 2, 1
      xx g, 4, 1, 1
      xx c, 5, 1, 0.6
      xx c, 5, 1.5, 1
      xx g, 4, 0.5, 0.5
      xx g, 4, 1, 1
End Sub
'----- 1音分の発声
'音名、オクターブ、拍数、継続時間の割合
Private Sub xx(noteName, octave, beat, ratio)
Dim cTime, rTime
      rTime = 60000 / bpm * beat
      cTime = rTime * ratio
      rTime = rTime - cTime
      notes(noteName).Ring octave, cTime, rTime
End Sub

SingSong() の引数 tuningPitch はチューニング時の A4 の周波数です。一般には 440Hz と言われているものの(それでデフォルトを 440 としました)、最近のオーケストラなどではちょっと高めが好まれているようで、N響は 442、かつてカラヤンは 446 だったそうです。
右は、音名とオクターブ番号とを楽譜上に示したものです。

tempo はメトロノームのビート数で、1分当たりの四分音符の拍数です。楽譜の右上などに などと書かれているものです。

SingSong() は一応、第九の4楽章、歓喜の歌の第2声の4小節のつもり・・・・・
そもそも周波数の設定が整数なのと、サブルーチンの呼び出しのオーバーヘッドが思いのほかかかる(多少、改善の余地はあります)のとで、音程はおぼつかないし、拍数も不安定なので、とても気持ちが悪いとも言えるし、それはそれで味があるとも言えます。ロバに乗ってダービーに臨んだようなものですね。ロバにはロバのよさがありますが。

しかしまあ、さっきの時報の真似くらいが無難なところと言えるでしょう。
ARDUINO 用に C++ で書いてまずまず成功したのを援用したのですが、VBA では、クラスを使わないで書いたほうが、もう少しまともになると思います。

ただし、1オクターブ分の SoundClass のインスタンスを作る CalcNote() までは有用で、これを用意しておけば、さっきの時報はこういう風によりエレガントに記述できます。

時報の改良版    SoundDemo2()

'Enum と notes() の宣言、CalcNote() は省略
'事前にどこかで CalcNote() を実行しておく

'----- 時報2
Public Sub TimeSignal2()
Dim n
      For n = 1 To 3
            notes(a).Ring 4, 100, 900
      Next
      notes(a).Ring 5, 1000, 0
End Sub

えっ? それほどエレガントでない? では、音を A ではなく H でやってみようとか、最後の音の前に短い Gis で装飾音を入れてみようとかいった場合にどうなるか、試してみてください。