0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PowerShell】MIDIでJR東日本の四点チャイムを鳴らす

0
Posted at

はじめに

前回の【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

実行結果

  • 以前よりもかなり四点チャイムらしい音になりました。
    • 管理者権限で実行しないと音が鳴らないかもしれません。

参考URL

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?