エクセルで「オーケストラ」を作って聞いてみた


何気なく、日曜日にテレ朝にチャンネルを合わせたら音楽をテーマした「関ジャム」という音楽番組でオーケストラを特集していた。

葉加瀬太郎さんがいろいろとオーケストラについて説明してくれていた。普段、疑問に思っていないことが、「さるほど」って「目から鱗」状態でした。

音楽は聴くだけだったので楽器にはあまり興味がなかったのですが、今回、何となくオーケストラを勉強してみたくなりました。

オーケストラの使い方






いまさら、楽器を演奏するのは無理そうなのでエクセル(Excel)で簡単な「オーケストラ」を作り、音をいじって楽器の位置でどう変わるかとか、効果を確認してみました。

使い方は簡単で楽器をボタンを押せば楽器毎に、下のピンクのボタンは複数の楽器を同時に鳴らすことができます。 

楽譜はエクセルシートに楽器毎に直接書き込む事で設定します。音源はPC内蔵のMIDI音源を使用しています。オーケストラの楽器に合わせて13種類の楽器にMIDI音源番号を割り当てています。例えば、バイオリンⅠは1番め目の楽器でMID音源番号は「41」、楽譜はMIDIのnote番号で設定していきます。




note番号「72」はC5(523.3Hz)になります。但し、「0」は何もしない、前の状態継続、「-1」は無音(midi的にはnote off)。

例えば 72 72 ⇒ 4分音符 4分音符
    72 0   ⇒ 2分音符
    72 -1  ⇒ 4分音符 無音 

初期楽譜は「チューリップの歌」でバイオリンパート以外は適当に私が書いた楽譜を設定してあります。

後は適当に自分で書き直せばmy「オーケストラ」の編曲を楽しむ事ができます。でも、PC内蔵のMIDI音源の音質にはあまり期待しないほうがいいと思います(笑)


現在、『MIDI 1.0規格書PDFダウンロードページ』で規格は無料で公開されています。これでMIDI規格は勉強できそうですが、PC画面で理解するのは大変という方は以下の本などが参考になると思います。






オーケストラのインストール


[開発]→[Visual Basic]でVB Editorを起動します。次に[挿入]→[標準モジュール]と[クラスモジュール]と[ユーザーフォーム]を挿入します。結果として以下のようなプロジェクトができます。初期ファイルでは必ず「Sheet1」名があると思いますが無い場合は作成しておきます。このシート名内に楽譜を記入する事になります。




それぞれのモジュールをクリックして以下のコードをモジュール毎にコピーして貼り付けます。但し、UserForm1だけは右クリックしてコードのエディタ画面を起動して下さい。


[Class1]

Option Explicit
Public WithEvents button1 As CommandButton
Public WithEvents button2 As CommandButton
Public WithEvents button3 As CommandButton
Public WithEvents button4 As CommandButton
Public WithEvents button5 As CommandButton
Public WithEvents button6 As CommandButton
Public WithEvents button7 As CommandButton
Public WithEvents button8 As CommandButton
Public WithEvents button9 As CommandButton
Public WithEvents button10 As CommandButton
Public WithEvents button11 As CommandButton
Public WithEvents button12 As CommandButton
Public WithEvents button13 As CommandButton
Public WithEvents button14 As CommandButton
Public WithEvents button15 As CommandButton
Public WithEvents button16 As CommandButton

Private Sub button1_Click()
    button1.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(1, 1)
    button1.BackColor = RGB(210, 210, 210)   
End Sub

Private Sub button2_Click()
    button2.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(2, 2)
    button2.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button3_Click()
    button3.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(3, 3)
    button3.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button4_Click()
    button4.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(4, 4)
    button4.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button5_Click()
    button5.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(5, 5)
    button5.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button6_Click()
    button6.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(6, 6)
    button6.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button7_Click()
    button7.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(7, 7)
    button7.BackColor = RGB(210, 210, 210)   
End Sub

Private Sub button8_Click()
    button8.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(8, 8)
    button8.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button9_Click()
    button9.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(9, 9)
    button9.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button10_Click()
    button10.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(10, 10)
    button10.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button11_Click()
    button11.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(11, 11)
    button11.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button12_Click()
    button12.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(12, 12)
    button12.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button13_Click()
    button13.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(13, 13)
    button13.BackColor = RGB(210, 210, 210)    
End Sub

Private Sub button14_Click()
    button14.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(1, 13)
    button14.BackColor = RGB(255, 128, 128)    
End Sub

Private Sub button15_Click()
    button15.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(1, 5)
    button15.BackColor = RGB(255, 128, 128)    
End Sub

Private Sub button16_Click()
    button16.BackColor = RGB(255, 128, 128)
    DoEvents
    Call FullPlay(6, 13)
    button16.BackColor = RGB(255, 128, 128)    
End Sub

[Module1]

Option Explicit
Declare Function midiOutOpen Lib "winmm.dll" (ByRef lphMidiOut As Long, ByVal uDeviceID As Long, ByVal dwCallback As Long, ByVal dwInstance As Long, ByVal dwFlags As Long) As Long
Declare Function midiOutShortMsg Lib "winmm.dll" (ByVal hMidiOutDevice As Long, ByVal dwMsg As Long) As Long
Declare Function midiOutClose Lib "winmm.dll" (ByVal hMidiOutDevice As Long) As Long
Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)

Public B(16) As String     '楽器名
Public C(15, 2) As Integer 'MIDI番号/楽器位置番号/左右バランス
Public D() As Integer      '楽譜


Sub FullPlay(ByVal No1 As Integer, ByVal No2 As Integer)
'フル演奏

    Dim hDevice, Note, VOL, BAL, CH, i, j, k, NN, LAST_ROW As Long

'MIDI音源番号(楽器)
    C(0, 0) = 0  'piano
    C(1, 0) = 41
    C(2, 0) = 41
    C(3, 0) = 42
    C(4, 0) = 43
    C(5, 0) = 44
    C(6, 0) = 48
    C(7, 0) = 57
    C(8, 0) = 72
    C(9, 0) = 74
    C(10, 0) = 58
    C(11, 0) = 72 'bassoon
    C(12, 0) = 69
    C(13, 0) = 61
    C(14, 0) = 0  '未使用
'楽器位置番号
    C(0, 1) = 0
    C(1, 1) = 1
    C(2, 1) = 2
    C(3, 1) = 3
    C(4, 1) = 4
    C(5, 1) = 5
    C(6, 1) = 6
    C(7, 1) = 7
    C(8, 1) = 8
    C(9, 1) = 10
    C(10, 1) = 11
    C(11, 1) = 12 'bassoon
    C(12, 1) = 13
    C(13, 1) = 14
    C(14, 1) = 15 '未使用
'左右音量バランス
    C(0, 2) = 64
    C(1, 2) = 0
    C(2, 2) = 32
    C(3, 2) = 96
    C(4, 2) = 114
    C(5, 2) = 127
    C(6, 2) = 0
    C(7, 2) = 54
    C(8, 2) = 34
    C(9, 2) = 14
    C(10, 2) = 74
    C(11, 2) = 94
    C(12, 2) = 114
    C(13, 2) = 127
    C(14, 2) = 64
    
    Const Volume  As Long = 32 '1~127
    Const Tempo = 140          'テンポ

'データ取得
    LAST_ROW = Sheets("Sheet1").Cells(Rows.Count, 2).End(xlUp).Row
    
    NN = LAST_ROW - 4
    ReDim D(NN + 1, 15) As Integer
    
    For j = 0 To 13
        For i = 1 To NN
            D(i, j + 1) = Sheets("Sheet1").Cells(i + 4, j + 2)
        Next i
    Next j
           
    Call midiOutOpen(hDevice, -1, 0, 0, 0)

    For i = 1 To NN
                       
        For k = No1 To No2     '使用楽器の設定
        
            BAL = C(k, 2)      '左右のバランス
            If k > 5 Then
                VOL = Int(0.9 * Volume) '後列の音量
            Else
                VOL = Volume            '前列の音量
            End If
            If D(i, k) = 0 Then
                VOL = 0
            ElseIf D(i, k) < 0 Then
                Call midiOutShortMsg(hDevice, 0 * 65536 + D(i - 1, k) * 256 + 128 + C(k, 1))
            End If

            Call midiOutShortMsg(hDevice, C(k, 0) * 256 + 192 + C(k, 1))               '楽器
            Call midiOutShortMsg(hDevice, VOL * 65536 + D(i, k) * 256 + 144 + C(k, 1)) '音符
            Call midiOutShortMsg(hDevice, BAL * 65536 + 10 * 256 + 176 + C(k, 1))      '左右
            
        Next k

        Sleep 60000 / Tempo
    
        For k = 0 To 15
            If D(i + 1, k) = 0 Then
            ElseIf D(i, 1) = 0 And D(i + 1, 1) > 0 Then
                Call midiOutShortMsg(hDevice, 0 * 65536 + D(i - 1, k) * 256 + 128 + C(k, 1))
            Else
                Call midiOutShortMsg(hDevice, 0 * 65536 + D(i, k) * 256 + 128 + C(k, 1))
            End If
        Next k

    Next i
    
'    For k = 0 To 15
'        Call midiOutShortMsg(hDevice, VOL * 65536 + D(NN, k) * 256 + 128 + C(k, 1))
'        Call midiOutShortMsg(hDevice, 64 * 65536 + 72 * 256 + 176 + C(k, 1))  'リリース時間
'        Sleep 60000 / (Tempo * 220)
'    Next k

    Call midiOutClose(hDevice)
   
End Sub

Sub NoteData()

'MIDI note Data
Dim Note(14) As String
Note(1) = "727476007274760079767472747674007274760072747600797674727476720079797679818179007676747472000000"
Note(2) = "849188918491889184918891839186918491889184918891849188918391869184918891849188918491839184000000"
Note(3) = "849188918491889184918891839186918491889184918891849188918391869184918891849188918491839184000000"
Note(4) = "606264-1606264-167646260626462-1606264-1606264-167646260626460-167676467696967-16464626260-1-1-1"
Note(5) = "727476007274760079767472747674007274760072747600797674727476720079797679818179007676747472000000"
Note(6) = "48-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-148-1"
Note(7) = "848688008486880091888684868886008486880084868800918886848688840091918891939391008888868684000000"
Note(8) = "727476007274760079767472747674007274760072747600797674727476720079797679818179007676747472000000"
Note(9) = "727476007274760079767472747674007274760072747600797674727476720079797679818179007676747472000000"
Note(10) = "606264-1606264-167646260626462-1606264-1606264-167646260626460-167676467696967-16464626260-1-1-1"
Note(11) = "727976797279767972797679717974797279767972797679727976797179747972797679727976797279717972000000"
Note(12) = "8080-1-18080-1-18080-1-18080-1-18080-1-18080-1-18080-1-18080-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1"
Note(13) = "-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-160-1-1"
Note(14) = "727476007274760079767472747674007274760072747600797674727476720079797679818179007676747472000000"

'MIDI timbre Number
Dim C(14, 0) As Integer
    C(0, 0) = 0  'piano
    C(1, 0) = 41
    C(2, 0) = 41
    C(3, 0) = 42
    C(4, 0) = 43
    C(5, 0) = 44
    C(6, 0) = 48
    C(7, 0) = 57
    C(8, 0) = 72
    C(9, 0) = 74
    C(10, 0) = 58
    C(11, 0) = 72
    C(12, 0) = 69
    C(13, 0) = 61
    C(14, 0) = 0

    Dim i, j As Integer
    For j = 0 To 13
        For i = 1 To Len(Note(1)) / 2
            Sheets("Sheet1").Cells(i + 4, j + 2) = Val(Mid(Note(j + 1), 2 * i - 1, 2))
       Next i
    Next j

    Sheets("Sheet1").Activate
    Columns(1).ColumnWidth = 15
    Range(Columns(2), Columns(14)).ColumnWidth = 3
    Cells(1, 1) = "No."
    Cells(2, 1) = "MIDI timbre No."
    Cells(4, 1) = "midi note No.    (楽器による音源範囲に注意 但し、[0]⇒前の状態継続、[-1]⇒無音)"
    For i = 1 To 13
        Cells(1, i + 1) = i
        Cells(2, i + 1) = C(i, 0)
    Next i

End Sub

[ThisWorkbook]


Private Sub Workbook_Open()
    Call NoteData
    UserForm1.Show 0 
End Sub

[UserForm1]

Option Explicit      
Public NewBtn As New Class1

Private Sub UserForm_Initialize()

Dim N As Integer
Dim B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14 As String
Dim LL1, TT1, TT2, WW, HH, FF As Integer
With UserForm1
    .Caption = "Excel Orchestra"
    .Height = 400
    .Width = 600
End With
  
    LL1 = 55
    TT1 = 200
    TT2 = TT1 - 180
    WW = 80
    HH = 100
    FF = 12
    B(0) = ""
    B(1) = "Violin I"   '41
    B(2) = "Violin II"  '41
    B(3) = "Viola"      '42
    B(4) = "Cello"      '43
    B(5) = "Contrabass" '44
    B(6) = "Timpani"    '48
    B(7) = "Trumpet"    '57
    B(8) = "Clarinet"   '72
    B(9) = "Flute"      '74
    B(10) = "Trombone"  '58
    B(11) = "Fagott"    '72 'bassoon
    B(12) = "Oboe"      '69
    B(13) = "Horn"      '61
    B(14) = "オーケストラ演奏"
    B(15) = "弦五部(前列)"
    B(16) = "管/打楽器(後列)"
    
    Set NewBtn.button1 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button1
        .Caption = B(1)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1
        .LEFT = LL1
        .Width = WW
        .Height = HH
    End With
    
    Set NewBtn.button2 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button2
        .Caption = B(2)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1 - 20
        .LEFT = LL1 + 100
        .Width = WW
        .Height = HH
    End With
    
    Set NewBtn.button3 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button3
        .Caption = B(3)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1 - 20
        .LEFT = LL1 + 200
        .Width = WW
        .Height = HH
    End With

    Set NewBtn.button4 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button4
        .Caption = B(4)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1
        .LEFT = LL1 + 300
        .Width = WW
        .Height = HH
    End With

    Set NewBtn.button5 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button5
        .Caption = B(5)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1
        .LEFT = LL1 + 400
        .Width = WW
        .Height = WW
    End With
    
'
    Set NewBtn.button6 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button6
        .Caption = B(6)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
       .Top = TT2 + 20
        .LEFT = LL1
        .Width = WW + 10
        .Height = WW
    End With

    Set NewBtn.button7 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button7
        .Caption = B(7)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2
        .LEFT = LL1 + 100
        .Width = WW + 10
        .Height = WW / 2
    End With

    Set NewBtn.button8 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button8
        .Caption = B(8)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2 + 50
        .LEFT = LL1 + 100
        .Width = WW + 10
        .Height = WW / 2
    End With

    Set NewBtn.button9 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button9
        .Caption = B(9)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2 + 100
        .LEFT = LL1 + 100
        .Width = WW + 10
        .Height = WW / 2
    End With
    
    Set NewBtn.button10 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button10
        .Caption = B(10)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2
        .LEFT = LL1 + 200
        .Width = WW + 10
        .Height = WW / 2
    End With

    Set NewBtn.button11 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button11
        .Caption = B(11)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2 + 50
        .LEFT = LL1 + 200
        .Width = WW + 10
        .Height = WW / 2
    End With

    Set NewBtn.button12 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button12
        .Caption = B(12)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2 + 100
        .LEFT = LL1 + 200
        .Width = WW + 10
        .Height = WW / 2
    End With
    
    Set NewBtn.button13 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button13
        .Caption = B(13)
        .BackColor = RGB(210, 210, 210)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT2 + 20
        .LEFT = LL1 + 300
        .Width = WW + 10
        .Height = WW
    End With
    
    Set NewBtn.button14 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button14
        .Caption = B(14)
        .BackColor = RGB(255, 128, 128)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1 + 110
        .LEFT = LL1 + 130
        .Width = WW + 60
        .Height = WW / 1.5
    End With
    
    Set NewBtn.button15 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button15
        .Caption = B(15)
        .BackColor = RGB(255, 128, 128)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1 + 120
        .LEFT = LL1 + 290
        .Width = WW + 20
        .Height = WW / 2
    End With
    
    Set NewBtn.button16 = UserForm1.Controls.Add("Forms.CommandButton.1")
    With NewBtn.button16
        .Caption = B(16)
        .BackColor = RGB(255, 128, 128)
        .Font.Name = "メイリオ"
        .Font.Size = FF
        .Top = TT1 + 120
        .LEFT = LL1 + 400
        .Width = WW + 20
        .Height = WW / 2
    End With    
    
End Sub




オーケストラのプログラム概要


ブログは基本テキストと画像データのみしか使えないため、普段はエクセルファイルで保持されるデータもVBAで書かないといけません。特に、ユーザーフォーム関係は結構大変でした。初めてユーザーフォームをすべてコードで書いたのでなかなか大変、何とか動いているようだけど...

ThisWorkbookの「Workbook_Open()」でエクセル起動時に動かすマクロ命令でシートに楽譜を書き込み、UserFormを表示させています。

UserForm1の「UserForm_Initialize()」で、このページの最初の画像のオーケストラを作画しています。このマクロはUserForm1が表示されると自動で読込まれて実行されるようです。

ここでフレームの大きさ、ボタンの形状と位置などを設定します。ボタンは16個あるので16のボタン情報を決めています。

普通はUserForm上に配置するボタンをクリックしてコマンドを設定していきますが、今回のようにすべてコードで描く場合はClass1モジュールを作成してボタン毎にマクロを関連つけていきます。

Module1は勿論、MIDI音源を鳴らすためのメインのコードです。
メインルーチンは4分音符を基準に回していて、テンポの速さは「140」にしています。

楽器の前列と後列で音量を少し変えています。後列は前列の90%の音量です。また、楽器の配置毎にMIDIの「Panpot」の設定で左右の音量の割合を少しずつ変えています。

これによって音が少しは立体的に聞こえるようになりました。


いつも、設定するときに忘れてしまうのですが、MIDIのmidiOutShortMsg関数の引数を10進数で表すと

midiOutShortMsg(hDevice, a * 65536 +b * 256 + c + d)

d:channel(0-15)
c:命令コード

16進数だと、&H9nとかでよく表されていますが、これは16進数表示で命令コードが「9」、チャンネルが「n」を表しています。チャンネルが「11」だとすると「9B」

これを10進数表示にすると「90→144、B→11」なのでd=11、c=144となります。

16進数でデータを送ればいいのでしょうが、私は10進数表示が好きなので、今回も、わざわざ、10進数に直して設定しています(笑)

VBAや楽器に関しては素人レベルの私が作ったプログラムなのでが、思ったより音楽を楽しめました。音楽の才能がないので初期データは少し酷い編曲になってしまいましたが、少しだけ作曲家気分を味わえました(笑)