はじめに
前回の【PowerShell】JR東日本の四点チャイムを鳴らすでは、「和音が使えない」「音色が選べない」という問題があったので、今回はMIDIを使ってこれらの問題を解決してみました。
作ったコード
- MIDIは全くの門外漢だったので、ChatGPTに聞きながらコードを作ってみました。
-
$signatureで定義されているのは、.NET(C#)のコードです。- ここでは「P/Invoke(Platform Invocation Services)」 という技術を使って、Windowsのネイティブなシステムファイルである winmm.dll(マルチメディアAPI)に用意されている関数を、.NET(およびPowerShell)から呼び出せるように橋渡しをしているそうです。
-
Add-Type -TypeDefinition $signatureでは、Add-Typeコマンドレットが文字列として定義されたC#のコード($signature)を その場でコンパイルして、PowerShell内で WinMIDI という.NETのクラスとして使えるようにしているそうです。- 「その場でコンパイルして」という部分については、MicrosoftのAdd-Typeのリファレンスにも書かれていなかったので、表現が正しくないかもしれません。
- Beepで指定できたのは「周波数と音の長さ」だけでしたが、MIDIでは「音階(MIDIのノートナンバー)、音の強さ、音の長さ、音色(プログラム・チェンジ)」ができるため、Beepとは比べものにならないほど表現の幅が広がります。
jr_east_midi.ps1
# WindowsのMIDI API(winmm.dll)を定義
$signature = @"
using System;
using System.Runtime.InteropServices;
public class WinMIDI {
[DllImport("winmm.dll")]
public static extern int midiOutOpen(out IntPtr handle, int deviceID, IntPtr proc, IntPtr instance, int flags);
[DllImport("winmm.dll")]
public static extern int midiOutShortMsg(IntPtr handle, int message);
[DllImport("winmm.dll")]
public static extern int midiOutClose(IntPtr handle);
}
"@
Add-Type -TypeDefinition $signature
# MIDIデバイスを開く
$handle = [IntPtr]::Zero
[WinMIDI]::midiOutOpen([ref]$handle, -1, [IntPtr]::Zero, [IntPtr]::Zero, 0) | Out-Null
# 音色(楽器)を変更する。
function Set-MidiInstrument {
param([int]$instrumentNumber)
# 0xC0 = Channel 1 Program Change
# instrumentNumber は 0~127 (GM仕様の番号より1引いた値)
$msg = 0xC0 -bor (($instrumentNumber - 1) -shl 8)
[WinMIDI]::midiOutShortMsg($script:handle, $msg) | Out-Null
}
# ノートオン(音を鳴らす)する。
function Send-MidiNote {
param([int]$note, [int]$velocity = 127)
# 0x90 = Channel 1 Note On
$msg = 0x90 -bor ($note -shl 8) -bor ($velocity -shl 16)
[WinMIDI]::midiOutShortMsg($handle, $msg) | Out-Null
}
# ノートオフ(音を止める)する。
function Stop-MidiNote {
param([int]$note)
# 0x80 = Channel 1 Note Off でベロシティ 0 を送ることで確実に消音する
$msg = 0x80 -bor ($note -shl 8) -bor (0 -shl 16)
[WinMIDI]::midiOutShortMsg($handle, $msg) | Out-Null
}
# 指定した音を鳴らす。
# chord: MIDIのノートナンバー。和音を指定できるように配列で渡している。
# velocity: 音の強さ(0~127)。
# milliSeconds: 音の長さ(ミリ秒)。
# instNumber: 音源の音色
function Sound{
param([int[]]$chord, [int]$velocity = 127, [int]$milliSeconds = 1000, [int] $instNumber)
Set-MidiInstrument -instrumentNumber $instNumber
foreach ($note in $chord) {
Send-MidiNote -note $note -velocity $velocity
}
# 指定時間鳴らす
Start-Sleep -Milliseconds $milliSeconds
# 音を止める
foreach ($note in $chord) {
Stop-MidiNote -note $note
}
}
# 四点チャイムを鳴らす(最後の1音は和音)
Sound -chord @(90) -velocity 127 -seconds 700 -instNumber 12
Sound -chord @(86) -velocity 127 -seconds 700 -instNumber 12
Sound -chord @(95) -velocity 127 -seconds 700 -instNumber 12
Sound -chord @(93, 88) -velocity 127 -seconds 1400 -instNumber 12
# MIDIデバイスを閉じる
[WinMIDI]::midiOutClose($handle) | Out-Null
実行結果
- 以前よりもかなり四点チャイムらしい音になりました。
- 管理者権限で実行しないと音が鳴らないかもしれません。