2014/12/11
PowerShellでMMLシーケンサーを作ってみた(その1)
MMLとは
この記事はPowerShell Advent Calendar 2014の11日目の記事です。
突然ですが、MMLってご存知ですか? MMLとはMusic Macro Languageの略で、その名の通り音楽演奏データを記述するための言語です。BASICのPLAY文で使うあれです。MIDIファイルに変換するコンパイラもありましたよね。
たとえば、「ねこふんじゃった」の最初の部分をMMLで書くとこんな感じです。MMLはいろんな記法があるのですが、よくあるのはこういうのです。
e-d-<g-4>g-rg-4e-d-<g-4>g-rg-4e-d-<g-4>g-4<e-4>g-4<d-4>frf4 e-d-<d-4>frf4e-d-<d-4>frf4e-d-<d-4>f4<e-4>f4<g-4>g-rg-4
ドの音をc、レの音をd、のように以下シの音をbまでアルファベット音階で割り当てます。半音上げる(シャープ)場合は音名のあとに+、半音下げる(フラット)場合は-を付与します。音の長さは4分音符なら4、8分音符なら8と書きます。付点は数字のあとに.を付けます。数字省略時は8分音符と解釈するのが普通のようです。オクターブを上げる場合は>、下げる場合は<と書きます(コンパイラによっては逆の解釈をする)。休符はrです。
本物のMMLでは、音色の指定だとかトラックの割り当てだとか、もっとたくさん命令があるんですが、基本はだいたいこんなもんです。
PowerShellで音を鳴らすには
PowerShellで音を鳴らすには、.NETのSystem.Console.Beepメソッドを使うのが一番手っ取り早いです。ビープ音ですね。比較的古いWindowsではハードウェアのビープスピーカーから、比較的新しいWindowsではサウンドデバイス上で鳴ります。今回のMMLシーケンサーも結局はBeepメソッドです。
そしてぎたぱそ先生がずっと以前に書いておられます。なので今回のはまあ、MMLシーケンサー部分を除けば二番煎じなんですけどもね。(そしてSmallBasicLibraryを使えば実はMML演奏もできるので、車輪の再発明でもあるんですが)
ちなみにwavやmp3なんかを鳴らすには、Windows Media PlayerをCOM経由で実行する方法や.NET(WPF)のSystem.Windows.Media.MediaPlayerクラスを使う方法などがあります。
音程の決め方
Beepメソッドでは第1引数にビープ音の周波数をHzで、第2引数に再生時間をミリ秒で指定します。つまりは音程(音の高さ)はHzで指定する必要があります。ここでドの音は何ヘルツで、みたいな対応表をどこかから探してきてそのデータを使ってもいいんですが、今回はせっかくなので、PowerShellで計算して求めてみます。
まず、ある音程の一つ上のオクターブの音程の間の周波数比は、1:2です。
ピアノ等の鍵盤楽器が近くにあれば、それを見てもらえば分かると思いますが、1オクターブには12の音程が含まれています。1オクターブの周波数比を12個均等に分割します(この周波数の決め方を平均律といいます)。つまり、隣り合う音程の周波数比は、1:12√2となるわけです。
さて、音程の周波数比はこれでわかりました。あとは基準となる音の周波数が分かれば、全ての音の周波数が計算できるわけです。時代や地域、演奏する楽器等によって微妙に違ってきますが、現代日本においては普通は、A(ラ)=440Hzが基準周波数です。
あとは単純にかけ算して周波数を求めて連想配列に入れておくだけです。コードにすればこんな感じですね。
$baseFrequency = 440.0 $pitches = @{ "c" = $baseFrequency * [math]::Pow(2, 3/12) "d" = $baseFrequency * [math]::Pow(2, 5/12) ... }
あと、+で半音上がる場合は[math]::Pow(2, 1/12)をかけ、-で半音下がる場合は割ればいいだけです。(別に$pitchesテーブルで最初から定義してもいいかもですが)
音価の決め方
音価というのは音の長さです。Beepメソッドの第2引数にはミリ秒で実際の時間を指定する必要があるので、これも計算で求めてやります。
4分音符というのは、1小節(全音符)を4分割、つまり1/4した音価を持ちます。同様に8分音符は1/8ですね。付点がつくと音価は1.5倍になります。
ではたとえば4分音符の具体的な音価はどうやって定めるのでしょうか。それを決めるのがテンポと拍子です。
テンポは1分間(つまり60000ミリ秒)の拍数(BPM、Bit Per Minuteともいう)で定義されます。今回作るシーケンサーは4/4拍子、すなわち1拍=4分音符とする4拍子固定とするので、1分間に4分音符がBPM回含まれることになります。
つまり、4分音符の音価=60000ミリ秒/BPMの値、全音符の音価=60000*4ミリ秒/BPMの値 となるわけです。
具体的なコードは…略します。単なるかけ算と割り算だけなんで。
MMLのパース
えっと時間がなくなってきたので、駆け足で説明します。
MMLのパースとは、要するに最初の例のようなMML(テキストデータ)から、実際に演奏する音程と音価の組み合わせのデータとして組み立てる作業になります。
今回のシーケンサーでは、繰り返し記号等はサポートしないので、単にMMLを一文字ずつ読み込んで、音名(cなど)を見つけたら、その後の文字を読んで、+があれば半音上げるなどして周波数を求め、その後に数字があればn分音符とみなし、先ほどの計算式から実際の音価を求めてやる。というのを全部の音でくり返すだけです。
この処理をやっているのがConvertFrom-MMLという関数です。MMLテキストを入力してやると、周波数(Frequency)や発音時間(Duration)などをプロパティとして含んだpscustomobject(Noteオブジェクト)を出力します。(Noteとは音符のことです)
MMLの演奏
パースしたMMLを実際に演奏するのがInvoke-Beep関数です。入力は、ConvertFrom-MMLで生成したNoteオブジェクトです。実際の処理は至って単純で、 [System.Console]::Beep($note.Frequency, $note.Duration)するだけです(えー)。あ、あと休符の時はDuration分だけSleepを入れてやっています。
MMLの>と<の意味を逆転する-Reverseスイッチ、BPMを指定する-Bpmパラメータ(デフォルト120)なんかも用意してます。
実際にはMMLから直接演奏できるように、Invoke-Mmlというラッパー関数を用意しています。
冒頭のねこふんじゃったのMMLを再生するにはこんな感じにします。
$s=@" e-d-<g-4>g-rg-4e-d-<g-4>g-rg-4e-d-<g-4>g-4<e-4>g-4<d-4>frf4 e-d-<d-4>frf4e-d-<d-4>frf4e-d-<d-4>f4<e-4>f4<g-4>g-rg-4 "@ Invoke-Mml $s
コード
function ConvertFrom-MML { [CmdletBinding()] param( [parameter(ValueFromPipeline=$true)][string[]]$mml, [int]$bpm = 120, [switch]$reverse = $false ) begin { $baseFrequency = 440.0 $scaleRatio = [math]::Pow(2, 1/12) $baseDuration = 60000 * 4 / $bpm $pitches = @{ "c" = $baseFrequency * [math]::Pow(2, 3/12) "d" = $baseFrequency * [math]::Pow(2, 5/12) "e" = $baseFrequency * [math]::Pow(2, 7/12) "f" = $baseFrequency * [math]::Pow(2, 8/12) "g" = $baseFrequency * [math]::Pow(2, 10/12) "a" = $baseFrequency * [math]::Pow(2, 12/12) "b" = $baseFrequency * [math]::Pow(2, 14/12) "r" = -1 } $currentOctave = 0 } process { $chars = [string[]]$mml.ToCharArray() 0..($chars.Length - 1)|%{ if($null -ne $pitches[$chars[$_]]) { $frequency = $pitches[$chars[$_]] $suffix = "" $denominator = "" $dot = $false if($_ -lt $chars.Length-1) { switch($chars[($_ + 1)..($chars.Length - 1)]) { {$pitches.Contains($_)} {break} "+" {$frequency *= $scaleRatio; $suffix += $_} "-" {$frequency /= $scaleRatio; $suffix += $_} {$_ -match "\d"} {$denominator += $_} "." {$dot = $true} } } $frequency *= [math]::Pow(2, $currentOctave) $denominator = [int]$denominator if($denominator -eq 0){$denominator = 8} $multiplier = 1 / $denominator if($dot){$multiplier *= 1.5} $duration = $baseDuration * $multiplier if($frequency -le 0){$on = $false}else{$on = $true} [pscustomobject]@{ Frequency = [int]$frequency Duration = $duration Note = "$($chars[$_])$suffix$denominator$(if($dot){"."})" Octave = $currentOctave Pitch = $chars[$_] Suffix = $suffix Denominator = "$denominator$(if($dot){"."})" On = $on } } elseif($chars[$_] -eq ">") { if($reverse) { $currentOctave-- } else { $currentOctave++ } } elseif($chars[$_] -eq "<") { if($reverse) { $currentOctave++ } else { $currentOctave-- } } } } } function Invoke-Beep { [CmdletBinding()] param([parameter(ValueFromPipeline = $true)][psobject[]]$note) process { if($note.On) { [System.Console]::Beep($note.Frequency, $note.Duration) } else { Start-Sleep -Milliseconds $note.Duration } } } function Invoke-Mml { [CmdletBinding()] param( [string]$mml, [int]$bpm, [switch]$reverse ) ConvertFrom-Mml @PSBoundParameters|Invoke-Beep }
おわりに
今回はMMLをパースしてBeepを演奏するスクリプトをPowerShellで作ってみました。これをシーケンサーというのはおこがましいと思いますが、まあたまにはこういう柔らかいネタもいいんじゃないでしょうか。ドレミの音がどうやって決められてるのかというのも、もしかして話のネタくらいにはなるかも。
そしてこれ、シーケンサーといいつつ、入力機能がないですよね。それについては次回やります。
PowerShellアドベントカレンダー2014はまだまだ残席ございます。ぜひ、あなたのPowerShell話を聞かせて下さい。
2008/02/19
ミク&サクラ勉強会ありがとうございました
先日エルおおさかで行われたわんくま勉強会におこし頂きありがとうございました。
ご報告が遅れましたがレポートです。
ふたコマいただいたんですが、ひとコマ目は音楽とDTM(MIDI)の基礎のお話をやりました。ドレミとは、音符とは、音階とは、和音とは、とかごく基礎のところを触れただけなので初心者の方には喜んでいただけたようですが、上級者の方には物足りなかったようで少し工夫をすべきでしたね。
後半はテキスト音楽「サクラ」を用いて実際に曲を作るところを3分間クッキング形式でどんどん作っていきました(実際はデータ作成に7,8時間かかっています)。なにより、ずったずったでドラムを打ち込めるところがウケてましたねwミク講演といっておきながらよく考えるとオケを作る方が時間かかるので実質サクラ講演に近くなってしまいました。でもミクの歌唱力に驚いた方もいらっしゃったようでよかったかなと。ミクの濃い話は私はちょっと無理なのでできればどなたかやっていただけると嬉しいなぁ、と思ったり。ミク単体で歌わせるのもいいですが、オケをつけるとぐっと「それっぽく」なることが示せたかな、と思います。
Audacityのファイルが壊れていた原因はよくわからないのですが、制作環境とデモ環境が違うといろいろ問題のようですね。どこかのファイルが絶対パスでデータを持ってたのが原因みたいです。一回デモ機でやったときはうまくいったんですけどねー?古いファイルを上書きしたのかも?
でもミク&サクラでこれだけのことができるということが示せてよかったように思います。アンケートもおおむね高評価をいただきました。ありがとうございます。これを機にDTMやってみよう、あるいは再開してみよう、サクラを触ってみようという方が結構おられたのがうれしかったです。あとミクのかわいい絵をアンケートに描いてくれた方どうもありがとうございますv
DTMと化学とスクリプトの関係は何かあるのかと聞かれたんですがとくにないですw
春編夏編とか、応用編とか、DTM本執筆とかわんくまテーマソング作曲とかいろいろ振られたんですけどごめんなさい、当分実現しそうにないですw
あと私の講演がやまにょんの濃いー講演にうまくつながったようでよかったです。私だけの話だと物足りなかった方もいたようですし。やまにょん遠いところからありがとー
講演資料、データ(完成版、作りかけのも含む)は一足先に自宅サーバーで公開しておきます。
mutaの作った音楽
http://winscript.jp/music/ (下の方)
ソフトのDL・紹介サイト
テキスト音楽「サクラ」 http://oto.chu.jp/
VOCALOID2 初音ミクhttp://www.crypton.co.jp/mp/pages/prod/vocaloid/cv01.jsp
Audacity http://audacity.sourceforge.net/
追伸。
この講演はまぁ、ちゃっぴさんが私をそそのかしたことがきっかけで実現したんですが、実はちゃっぴさんから贈り物が届いていました。最後にちょっとだけみなさんにお見せしたんですけど家で写真撮りました。
こんなメッセージが入っていました。これ会場で読むべきだったんですがてんぱってて読めませんでした、ごめんなさい
えーと。これ、「式場 エルおおさか」 で届いたんですけど・・・wwwww
というか、これはあれですね、結婚したいしたいと某所でわめいていた私に、じゃあミクと結婚しちゃえばというちゃっぴ先生のきっついシャレですなw
というわけで、みなさんどうもありがとうございました。
次回は3/29 PowerShellの話をやりますのでよろしくお願いします。
元記事:http://blogs.wankuma.com/mutaguchi/archive/2008/02/19/123810.aspx
Copyright © 2005-2018 Daisuke Mutaguchi All rights reserved
mailto: mutaguchi at roy.hi-ho.ne.jp
プライバシーポリシー