tech.guitarrapc.cóm

C#, PowerShell, Unity, Cloud, Serverless Technical Update and Features

PowerShellでSmallBasicLibrary.dllを使ってMMLにて「ドレミの歌」を奏でてみよう

前回は、ビープ音で「ドレミの歌」を奏でました。
PowerShellでビープ音の「ドレミの歌」を奏でてみよう
ところが、Beep音は同時に鳴らすことができないため、和音は奏でられません。 WindowsではWMVの再生などは.Netで出来ますが、どうも音を作って鳴らす事が難しそうです。 そこで他の簡単に利用できそうな手段を探して見ると……あるじゃ無いですかSmallBasicLibrary.dllでMMLが奏でられるようです。
C#でドレミファソなどの音階を再生することはできるでしょうか
今回は、PowerShellでSmallBasicLibrary.dllを参照してMMLを奏でてみます。

Small Basicとは

細かいことは調べていただいて(おい)、簡単にいうと無料で利用できる「マイクロソフトの学習用プログラミング言語である。」とのことです。(Wikipediaより) 昔私もちょろっと触って、プログラムの世界に迷いこんだ記憶が…。
Wikipedia - Small Basic
公式ページにはこうあります。
MSDN - Get Started with Small Basic Microsoft Small Basic puts the fun back into computer programming. With a friendly development environment that is very easy to master, it eases students of all ages into the world of programming.

SmallBasicをダウンロードしてSmallBasicLibrary.dllを取得しよう

1. Microsoftの公式ページから最新版のSmall Basicをダウンロードしてください。(本メソッドが含まれるのは0.9かららしいです。) ※05/Feb/2013現在の最新バージョンは1.0です。
MSDN - Get Started with Small Basic
2. SmallBasicをインストールします。 SmallBasic 3. SmallBasicLobrary.dllが存在する事を確かめます。 SmallBasicLibrary.dll ※私はスクリプトのフォルダにコピーして参照に追加しています。

ps1でSmallBasicLibrary.dllを参照に追加する

PowerShellにはAdd-Typeという力強い味方が、PowerShell V2.0から追加されました。 これにより、外部dllを容易に参照追加できます。
今回は、以下のようにスクリプトパスにSmallBasic1.0フォルダを作って、その中にSmallBasicLibrary.dllを入れています。
参照追加するには、以下の一文で完了します。
Add-Type -Path ".\SmallBasic1.0\SmallBasicLibrary.dll"

PlayMusic()メソッドを呼び出す

あとは、メソッドを呼び出します。 SmallBasicブログによるとMMLを奏でる為のメソッドは、.PlayMusic()です。
The Official Blog of Small Basic - Small Basic V0.9 is here!
.PlayMusic()メソッドは、Microsoft.SmallBasic.Library.Soundにあるようです。 よって、PowerShellでメソッドを呼び出すには、以下のように記述します。
[Microsoft.SmallBasic.Library.Sound]::PlayMusic( "MML記述" )

MMLで記述する

SmallBasicLibrary.dllへ音を渡すには、MMLのサブセットとして記述する必要が有るようです。
The Sound object now has a new operation, PlayMusic, which plays music described by a subset of Music Markup Language supported by QBasic. An example is: Sound.PlayMusic("O5 C8 C8 G8 G8 A8 A8 G4 F8 F8 E8 E8 D8 D8 C4")
MMLの記述はここが分かりやすかったです。
猿頁 - (86) Sound.PlayMusic で使える要素
PowerShellで渡すならこうですね。
"O5 C3 D8 E3 C8 E4 C4 E2" | %{[Microsoft.SmallBasic.Library.Sound]::PlayMusic( $_ )}

Get-PlaymusicでFunction化しておく

前回同様、一々メソッドを書いていられないのでfunction化してしまいます。 今回は、MMLとして[string]型で渡しますので、filterでコレクション処理する必要はありません。 よって、functionとします。
function Get-Playmusic {
	
	param(
	[Parameter(ValueFromPipeline=$true)]
	[string]$keyNote
	)
	
	[Microsoft.SmallBasic.Library.Sound]::PlayMusic( $keyNote )
	
}

Doremiの歌もFunction化して実行

あとは、楽譜通りに入力するだけです。 コード全貌はこの通り! Beep()に比べてずいぶんとすっきりしましたw
Add-Type -Path ".\SmallBasic1.0\SmallBasicLibrary.dll"

function Get-Playmusic {
	
	param(
	[Parameter(ValueFromPipeline=$true)]
	[string]$keyNote
	)
	
	[Microsoft.SmallBasic.Library.Sound]::PlayMusic( $keyNote )
	
}

function doremi-song {

	param(
	)
	
	process{
	
		"O5 C3 D8 E3 C8 E4 C4 E2" | Get-Playmusic
		"O5 D3 E8 F8 F8 E8 D8 F1" | Get-Playmusic
		"O5 E3 F8 G3 E8 G4 E4 G2" | Get-Playmusic
		"O5 F3 G8 A8 A8 G8 F8 A1" | Get-Playmusic
		"O5 G3 C8 D8 E8 F8 G8 A1" | Get-Playmusic
		"O5 A3 D8 E8 F#8 G8 A8 B1" | Get-Playmusic
		"O5 B3 E8 F#8 G#8 A8 B8 > C1.5" | Get-Playmusic
		"O5 B8 B-8  A4 F4 B4 G4 > C1" | Get-Playmusic
		sleep -m 400
		"O5 C4 D4 E4 F4 G4 A4 B4" | Get-Playmusic
		"O6 C4 C4 < B4 A4 G4 F4 E4 D4" | Get-Playmusic
		"O5 C4 E4 E2 E4 G4 G2" | Get-Playmusic
		"O5 D4 F4 F2 A4 B4 B2" | Get-Playmusic
		"O5 C8 E8 E4 E8 G8 G4" | Get-Playmusic
		"O5 D8 F8 F4 A8 B8 B4" | Get-Playmusic
		"O5 G2 C2 A2 F2 E2 C2 D1" | Get-Playmusic
		"O5 G2 C2 A2 B2 > C2 D2 C1" | Get-Playmusic
		
	}
}

doremi-song

和音はできるのか

さて、肝心の和音ですがMMLはもくろみ通り別々に実行すると和音として奏でられます。 しかし、コマンドを非同期に「バックグラウンドでなく」実行する方法がナカナカ思いつかず…。 最終的には、PowerGUIでexeにコンパイルして非同期に実行させようと思います。

楽譜はどこだ

Youtubeにあった、楽譜のピアノ譜面「右手と左手」でいこうかと思います。
「ドレミの歌 ポップスver」(大合奏バンドブラザーズDX)+楽譜

PowerGUIでコンパイルする際の注意

SmallBasicLibrary.dllをコンパイル時の参照に追加しますが、パスがスクリプトと同一パスになります。 よって、以下のように参照を書き換えます。 これでスクリプトを今まで通り実行してもコンパイルして実行しても平気ですね。
function Add-dllSmallBasic{
	
	param(
	)
	
		if (Test-Path ".\SmallBasic1.0\")
		{
			Add-Type -Path ".\SmallBasic1.0\SmallBasicLibrary.dll"
		}
		else
		{
			Add-Type -Path ".\SmallBasicLibrary.dll"
		}
}	

Add-dllSmallBasic

右手のスクリプト

では、後は繰り返しなのでどーんと。 1行/1小節で記述しています。
#requires -Version 2.0

#notes
$whole = 1600 #全音符
$half = $whole / 2 #2分音符
$fter = $whole / 4 #4分音符
$qver = $whole / 8 #8分音符

$haft = $half + $fter #付点付2分音符
$ftqv = $fter + $qver #付点付4分音符

#rest
$wholerest = {sleep -m $whole} #全休符
$halfrest = {sleep -m $half} #2分休符
$fterrest = {sleep -m $fter} #4分休符
$qverrest = {sleep -m $qver} #8分休符

function Add-dllSmallBasic{
	
	param(
	)
	
		if (Test-Path ".\SmallBasic1.0\")
		{
			Add-Type -Path ".\SmallBasic1.0\SmallBasicLibrary.dll"
		}
		else
		{
			Add-Type -Path ".\SmallBasicLibrary.dll"
		}
}	


function Get-Playmusic {
	
	param(
	[Parameter(ValueFromPipeline=$true)]
	[string]$keyNote
	)
	
	[Microsoft.SmallBasic.Library.Sound]::PlayMusic( $keyNote )
	
}

function doremi-song {

    param(
    )

    begin{
    }
	
    process{
				
		. $wholerest
		. $wholerest

		"O6 C3 D8 E3 C8" | Get-Playmusic
		"O6 E4 C4 E2" | Get-Playmusic
		"O6 D3 E8 F8 F8 E8 D8" | Get-Playmusic
		"O6 F1" | Get-Playmusic
		"O6 E3 F8 G3 E8" | Get-Playmusic
		"O6 G4 E4 G2" | Get-Playmusic
		"O6 F3 G8 A8 A8 G8 F8" | Get-Playmusic
		"O6 A1" | Get-Playmusic
		"O6 G3 C8 D8 E8 F8 G8" | Get-Playmusic
		"O6 A3 C8 D8 E8 F8 G8" | Get-Playmusic
		"O6 A3 D8 E8 F#8 G8 A8" | Get-Playmusic
		"O6 B3 D8 E8 F#8 G8 A8" | Get-Playmusic
		"O6 B3 E8 F#8 G#8 A8 B8" | Get-Playmusic
		"O7 C2 C4 < B8 B-8" | Get-Playmusic
		"O6 A4 F4 B4 G4" | Get-Playmusic
		"O7 C1" | Get-Playmusic
		. $fterrest
		"O6 C4 D4 E4" | Get-Playmusic
		"O6 F4 G4 A4 B4" | Get-Playmusic
		"O7 C4 C4 < B4 A4" | Get-Playmusic
		"O6 G4 F4 E4 D4" | Get-Playmusic
		"O6 C4 E4 E2" | Get-Playmusic
		"O6 E4 G4 G2" | Get-Playmusic
		"O6 D4 F4 F2" | Get-Playmusic
		"O6 A4 B4 B2" | Get-Playmusic
		"O6 C8 E8 E4 E8 G8 G4" | Get-Playmusic
		"O6 D8 F8 F4 A8 B8 B4" | Get-Playmusic
		"O6 G2 C2" | Get-Playmusic
		"O6 A2 F2" | Get-Playmusic
		"O6 E2 C2" | Get-Playmusic
		"O6 D1" | Get-Playmusic
		"O6 G2 C2" | Get-Playmusic
		"O6 A2 B2" | Get-Playmusic
		"O7 C2 D2" | Get-Playmusic
		"O7 C1" | Get-Playmusic

		"O6 C3 D8 E3 C8" | Get-Playmusic
		"O6 E4 C4 E2" | Get-Playmusic
		"O6 D3 E8 F8 F8 E8 D8" | Get-Playmusic
		"O6 F1" | Get-Playmusic
		"O6 E3 F8 G3 E8" | Get-Playmusic
		"O6 G4 E4 G2" | Get-Playmusic
		"O6 F3 G8 A8 A8 G8 F8" | Get-Playmusic
		"O6 A1" | Get-Playmusic
		"O6 G3 C8 D8 E8 F8 G8" | Get-Playmusic
		"O6 A3 C8 D8 E8 F8 G8" | Get-Playmusic
		"O6 A3 D8 E8 F#8 G8 A8" | Get-Playmusic
		"O6 B3 D8 E8 F#8 G8 A8" | Get-Playmusic
		"O6 B3 E8 F#8 G#8 A8 B8" | Get-Playmusic
		"O7 C2 C4 < B8 B-8" | Get-Playmusic
		"O6 A4 F4 B4 G4" | Get-Playmusic
		"O7 C2 C4 C8 C8" | Get-Playmusic
		"O6 A2 F2" | Get-Playmusic
		"O6 B2 G2" | Get-Playmusic
		"O7 C1" | Get-Playmusic
		. $fterrest
		"O6 C4 D4 E4" | Get-Playmusic
		"O6 F4 G4 A4 B4" | Get-Playmusic
		"O7 C4" | Get-Playmusic
		. $fterrest
		"O6 G4" | Get-Playmusic
		. $fterrest
		"O7 C1" | Get-Playmusic
    }

    end{
    }
}

Add-dllSmallBasic
doremi-song

左手のスクリプト

どどどーんと。 1行/1小節で記述しています。 Start-Sleep -Milliseconds 33.5はコンパイルして非同期に実行した際の右手のスクリプトとのタイムラグを埋めるためです。
#requires -Version 2.0

#notes
$whole = 1600 #全音符
$half = $whole / 2 #2分音符
$fter = $whole / 4 #4分音符
$qver = $whole / 8 #8分音符

$haft = $half + $fter #付点付2分音符
$ftqv = $fter + $qver #付点付4分音符

#rest
$fterrest = {sleep -m $fter} #4分休符
$qverrest = {sleep -m $qver} #8分休符

function Add-dllSmallBasic{
	
	param(
	)
	
		if (Test-Path ".\SmallBasic1.0\")
		{
			Add-Type -Path ".\SmallBasic1.0\SmallBasicLibrary.dll"
		}
		else
		{
			Add-Type -Path ".\SmallBasicLibrary.dll"
		}
}	


function Get-Playmusic {
	
	param(
	[Parameter(ValueFromPipeline=$true)]
	[string]$keyNote
	)
	
	[Microsoft.SmallBasic.Library.Sound]::PlayMusic( $keyNote )
	
}

function doremi-song {

    param(
    )

    begin{
    }
	
    process{
		
		"O5 C8 E8 G8 E8 > C8 < G8 > C8 G8" | Get-Playmusic
		. $qverrest
		"O7 C8 < B8 A8 G8 F8 E8 D8" | Get-Playmusic
		
		"O5 C8 E8 G8 E8 > E8 < E8 G8 E8" | Get-Playmusic
		"O5 C8 E8 G8 E8 > E8 < E8 G8 E8" | Get-Playmusic
		"O5 D8 G8 B8 G8 > D8 < G8 B8 G8" | Get-Playmusic
		"O5 D8 G8 B8 G8 > D8 < G8 B8 G8" | Get-Playmusic
		"O5 E8 G8 > C8 < G8 > E8 < G8 > C8 < G8" | Get-Playmusic
		"O5 E8 G8 > C8 < G8 > E8 < G8 > C8 < G8" | Get-Playmusic
		"O5 F8 A8 > C8 < A8 > F8 < A8 > C8 < A8" | Get-Playmusic
		"O5 F8 A8 > C8 < A8 > F8 < A8 > C8 < A8" | Get-Playmusic
		"O5 G8 > C8 E8 C8 G8 C8 E8 C8" | Get-Playmusic
		"O5 A8 > C8 F8 C8 A8 C8 F8 C8" | Get-Playmusic
		"O5 A8 > D8 F#8 D8 A8 D8 F#8 D8" | Get-Playmusic
		"O5 B8 > D8 G8 D8 B8 D8 G8 D8" | Get-Playmusic
		"O5 B8 > E8 G#8 E8 B8 E8 G#8 E8" | Get-Playmusic
		"O6 C8 E8 A8 E8 > C4 < B8 B-8" | Get-Playmusic
		"O6 A2 B2" | Get-Playmusic
		"O7 C4 < G4 E4 D4" | Get-Playmusic
		
		"O6 C4 C4 D4 E4" | Get-Playmusic
		"O6 F4 G4 A4 B4" | Get-Playmusic
		"O7 C4 C4 < B4 A4" | Get-Playmusic
		"O6 G4 F4 E4 D4" | Get-Playmusic
		"O6 C8 < G8 > C8 E8 E8 C8 < G8 > C8" | Get-Playmusic
		"O6 E8 C8 E8 G8 G8 E8 C8 E8" | Get-Playmusic
		"O6 D8 < B8 > D8 F8 F8 D8 < B8 > D8" | Get-Playmusic
		"O6 A8 F8 A8 B8 B8 G8 D8 G8" | Get-Playmusic
		"O6 C8 < G8 > C8 E8 E8 C8 E8 G8" | Get-Playmusic
		"O6 D8 < B8 > D8 F8 A8 F8 A8 B8" | Get-Playmusic
		"O6 E1" | Get-Playmusic
		"O6 F1" | Get-Playmusic
		"O6 E2 E4 G4" | Get-Playmusic
		"O6 D1" | Get-Playmusic
		"O6 E1" | Get-Playmusic
		"O6 F2 G2" | Get-Playmusic
		"O6 G2 A2" | Get-Playmusic
		"O6 G2 G8 F8 E8 D8" | Get-Playmusic
		
		"O5 C8 E8 G8 E8 > E8 < E8 G8 E8" | Get-Playmusic
		"O5 C8 E8 G8 E8 > E8 < E8 G8 E8" | Get-Playmusic
		"O5 D8 G8 B8 G8 > D8 < G8 B8 G8" | Get-Playmusic
		"O5 D8 G8 B8 G8 > D8 < G8 B8 G8" | Get-Playmusic
		"O5 E8 G8 > C8 < G8 > E8 < G8 > C8 < G8" | Get-Playmusic
		"O5 E8 G8 > C8 < G8 > E8 < G8 > C8 < G8" | Get-Playmusic
		"O5 F8 A8 > C8 < A8 > F8 < A8 > C8 < A8" | Get-Playmusic
		"O5 F8 A8 > C8 < A8 > F8 < A8 > C8 < A8" | Get-Playmusic
		"O5 G8 > C8 E8 C8 G8 C8 E8 C8" | Get-Playmusic
		"O5 A8 > C8 F8 C8 A8 C8 F8 C8" | Get-Playmusic
		"O5 A8 > D8 F#8 D8 A8 D8 F#8 D8" | Get-Playmusic
		"O5 B8 > D8 G8 D8 B8 D8 G8 D8" | Get-Playmusic
		"O5 B8 > E8 G#8 E8 > D8 < E8 G#8 E8" | Get-Playmusic
		"O6 C8 E8 A8 E8 > C4 < B8 B-8" | Get-Playmusic
		"O6 A2 B2" | Get-Playmusic
		"O7 C2 C4 C8 C8" | Get-Playmusic
		"O6 A1" | Get-Playmusic
		"O6 B1" | Get-Playmusic
		"O7 C1" | Get-Playmusic
		. $fterrest
		"O6 C4 D4 E4" | Get-Playmusic
		"O6 F4 G4 A4 B4" | Get-Playmusic
		"O7 C4" | Get-Playmusic
		. $fterrest
		"O6 G4" | Get-Playmusic
		. $fterrest
		"O7 C1" | Get-Playmusic
	
    }

    end{
    }
}

Add-dllSmallBasic
Start-Sleep -Milliseconds 33.5
doremi-song

コンパイル

前回の記事で紹介したように、PowerGUIで右手と左手のスクリプトをそれぞれコンパイルします。 PowerGUI Complile

コンパイルしたファイルを非同期に実行する

簡単です。Start-Processコマンドレットで実行するだけです。
Start-Process ".\SmallBasic-cmdlt_Second.exe"
Start-Process ".\SmallBasic-cmdlt.exe"
あるいは、以下のサイトで紹介されている方法もありますが変わりません。
PowerShell で外部プログラムを非同期で起動する
この場合、こうでしょうか。
$process = New-Object Diagnostics.Process
$process.StartInfo.FileName = ".\SmallBasic-cmdlt_Second.exe"
$process.StartInfo.Arguments = $null
$process.Start()

$process = New-Object Diagnostics.Process
$process.StartInfo.FileName = ".\SmallBasic-cmdlt.exe"
$process.StartInfo.Arguments = $null
$process.Start()
こういう感じで実行してます。 Run

実行結果

単音を重ねて「和音」は出たんですが……タイミングが合わないですねwww Sleepで、スクリプトの実行タイミングを調整しても実行毎に変わります。 また、実行中に左手用のスクリプトが徐々に遅れていって、最終的に4分音符程度まで遅れます。 一行毎のステップ実行では同期が取れているのでどうやらこうやらです。

まとめ

あと一歩だったんですが楽しかったのでいいです。 興味があったら、だれか完成させてください (おい