PurentroをWindows RTで動かすために,Windows RTでのMIDIサポートを調べる。結果,無理と言うことが分かった。いろいろ調べていたら,結構WPFとWindows RTは違うのか。じゃあ何を作ろうかなあ。
‘Purentro’のエントリ
CNETのdownload.comに登録したら他のサイトからも「登録しましたが何か?」的なメールが届いた。日本でもVectorに登録するといろいろ登録されるのでこの辺は同じか。
Softpedia。Purentro。紹介用にコメントとかスクリーンショットもちゃんと書いてる。なんかついでにWorld Testerも登録されてた。わざわざDenasuページからひろってきてるのか。
Brothersoft。Purentro。これはCNETそのままパクってる感じ。
他にアラビア語のサイトとかあったけどよく分からない。
Purentro。折角英語版を作ったので海外サイトに登録しよう! てなわけでググってみるとCNETのdownload.comが最大手らしい。登録方法が非常に分かりにくいが,upload.comという別サイトで登録するようだ。適当に英語で説明を書いて申請したところ,アカウント登録に1日,作者登録に1日,ソフト登録に1日で完了。CNETのPurentroページ。とりあえずサポート掲示板を英語対応しておいた。
「MIDI プレイヤー & 練習ソフト Purentro Ver.1.0」を公開。新作キーボード練習ソフト! といってもMIDIキーボードだけど・・・。
今回はマニュアルにおまけ情報を書けなかったのでここに書いてしまおう。
このプロジェクトは,元々10年前に某鳥の名前のファミレスでMIDIキーボード練習ソフト作ろーみたいな話があって,いつの間にか立ち消えになったもの。1年くらい前からひっそり再開して作っていたのだが,とりあえず作りたかったものができたので公開してみた。
内容としては,.midファイルを読み込んで楽譜を表示し,MIDIキーボードがあれば練習にも使えるというソフト。ということでこれを使う人はだいぶ限られそうなので環境とか何も考えずに作っている。XPは動くけど非サポート扱いにしたし,.NET 4.0必須だし,ディスプレイの解像度は1920×1200以上じゃないと厳しいし。要は自分の環境に最適化したという話。
技術的な話も。まず,マニュアルにも言い訳を書いているが,MIDIファイルは楽譜ファイルじゃないのでちょっと複雑なMIDIファイルだとお手上げ。発音のタイミングが音符の位置からずれまくるし,1つのトラックに複数のパートがあると見分けが付かないし,世にある楽譜化ソフトも相当苦労しているはずだけど,完璧なものはないですね。この辺の苦労をはっぱさんと語れたのが今回最大の収穫。そういえばはっぱさんからはiPadでやったらいいよとかメモ書き機能があるといいよとかアドバイスをもらった。次バージョンがあれば考えてみよう。音楽は何も知らないのでそういう人が近くにいると心強いですね。
あとはWPFですかね。最初はC++ Direct Drawで作ってたけど,斜め線が引けなくてDirect3Dに切り替えて,その後きれいに拡大縮小をしたくてC# WPFに切り替えた。以前Silverlightを少し触っていたときはあまり感じなかったけど,WPFを本格的にやってみるとかなり面白い。これだけの開発環境が揃っているといろいろ作りたくなってくる。Windows PhoneはSilverlight(=WPFの軽量版みたいなもの)なので,いろいろやってみたいかも。
ところでPurentroって未だに意味がわからないんですけど,どういう意味ですか?>ぱじ
MIDIキーボードタイピングソフトができてきたところでそろそろテスト工程に入る。まずはXP実機で試してみる。以前VMWareで実験したときはエラーが出て実行できなかったが,実機というか.NET4.0にしたおかげか実行はできた。ただ,設定ファイルにハッシュアルゴリズムSHA256を使っていたのがXPではサポートされていないらしく設定ファイルを読むところでエラーになった。とりあえずセキュリティ的に重要なわけではないのでSHA1で我慢しておくか。
C#でMIDIキーボード入力をやってみる。midiInOpenにコールバックを登録しておけば,midiInStart/midiInStopでメッセージを受け取れる。
{ [DllImport("winmm.dll")] public static extern uint midiOutOpen( ref IntPtr phmo, uint uDeviceID, IntPtr dwCallback, IntPtr dwInstance, uint fdwOpen); public delegate void CallBackDelegate( IntPtr hdrvr, uint uMsg, uint dwUser, uint dw1, uint dw2); } public class MIDIIn { private Win32MIDI.CallBackDelegate _callback = null; public void Open() { _callback = new Win32MIDI.CallBackDelegate(CallBack); Win32MIDI.midiInOpen( ref _midiin, _mididevice, (IntPtr)Marshal.GetFunctionPointerForDelegate(_callback), (IntPtr)0, Win32MIDI.CALLBACK_FUNCTION); } private void CallBack(IntPtr hdrvr, uint uMsg, uint dwUser, uint dw1, uint dw2) { int state = (int)dw1 & 0xff; int data1 = (int)dw1 >> 8 & 0xff; int data2 = (int)dw1 >> 16 & 0xff; } }
最初,C++で実装するときと同じようにCallBackをstaticメソッドにしていたのだが,インスタンスのメンバ変数を使いたくて,midiInOpenのdwInstanceにthisを渡すことを考えていた。でもthisをIntPtrに変換する方法というか変換していいのかを調べても何も情報は見つからない。最終的に「GetFunctionPointerForDelegateはthisポインタをうまいこと扱ってくれる」というような記述を見つけて,思いきってstaticを取ったらそのまま動いた。C++の発想だとstaticにしないと動かないのでメンバ関数をそのまま渡すというのは思いつきもしなかった。こんな感じで半日を無駄にしてしまった。
その後は順調に進んで,キーボード練習ソフトっぽいものはできあがった。そろそろもばもば(?)に投げてみよう。
MIDI楽譜表示。UIはだいたいできてきた。今こんな感じ。
C++からC#への移行も兼ねてWPFで作ってるのだが,WPFで実装していくと,作った覚えのない部分が勝手に実装されていたりするのが気持ちいい。例えば下のプログレスバーは演奏箇所にBindingしているので演奏が進むと勝手にバーが進むようになっている。で,それとは別に楽譜の中にある赤いカーソルは,演奏している場所に合わせて描画している。こうしておくとプログレスバーを動かすとリアルタイムに赤いカーソルが動くようになる。そんなコードは作り込んでないのに勝手に動くのが素晴らしい。
とはいえ,今までのWin32プログラミングと比べて,どれだけコードをかかずにやりたいことを実現させるか,みたいなのに時間を使ってしまうので無駄に手間がかかってるような。
先週は右側のトラック表示部分をドラッグして並べ替えられるようにしていた。先月のMSDN Magazineにいい感じの記事があったので,それを参考に実装はできたのだが,ItemContainerGeneratorとか深い部分はまだ分かってない感じ。
最後にはまったのがドラッグ時のアニメーション。StoryboardのDurationにmillisecondを指定しても,アニメーションが途中で終わってしまって困ったのだが,StoryboardのDurationではなくてDoubleAnimationとかのDurationに指定しないといけないのか。Storyboardは0.3秒とかでもDoubleAnimationがデフォルトの1秒になってて途中で切れていたっぽい。
UIが完成したらもう一度楽譜に戻って未実装部分を作り込んで,とりあえず楽譜表示は完成か。
WPFでボタンをデザイン。C++MFCだとオーナードローでこんなボタンを作ったらそれだけで数日はかかりそうだが,C#+WPF+Expression Blendなら慣れれば1時間くらいでできそう。今までめんどくさくてUIに凝ることもなかったが,これだけの開発環境を用意されるとUI作るのが楽しくなってくる。
ちなみに作り方をメモっておく。Expression Blendで普通のボタンを作ってテンプレートの編集。ボタンの中身をごっそり削除して,Ellipseを配置してグラデーションをいい感じに設定。中身にcontaintPresenterをはりつけておく。ここまででデザインは完了。これだとボタンを押しても見た目が変わらないので,トリガータブの「+プロパティ」でIsPressedとかIsMouseOverとか必要なものを追加して,デザインを適当にいじるとイベントが起きたときに差分が適用されるようになる。ここまででスタイル作成完了。Mainのウィンドウに戻って今作ったスタイルをボタンのStyleとして設定。最後にボタンの中身にExpression Designとかで作った絵を貼り付けて完成。
ついでにツールチップも付けてみた。色合いを変えて半透明にするためにStyleを変更してみたが,以下のようなコードを書くと,Expression Blendで編集する際に「’ToolTip’は,論理親またはビジュアル親を持つことができません。」というエラーになる。Expression Blendで編集できないだけでVisual Studio 2010ではビルドできて普通に動くのだが,これでいいのだろうか。
<ResourceDictionary> <Style x:Key="Tip" TargetType="{x:Type ToolTip}"> <Setter Property="Background" Value="#202020"/> <Setter Property="Foreground" Value="White"/> <Setter Property="Opacity" Value="0.8"/> </Style> </ResourceDictionary> ---- <Window> <Button Style="{DynamicResource PanelButton}"> <Button.ToolTip><ToolTip Style="{DynamicResource Tip}">次のページ</ToolTip></Button.ToolTip> <Canvas Width="16" Height="16" Background="{DynamicResource NextButton}"/" </Button> </Window>
楽譜表示。何度も作り替えているが,結局WPFで作ることに。ベクターグラフィックをそのまま使えるので拡大縮小しても綺麗なままというのがいい。2000だと動かない,XPだと汚くなるとかあるけど,もはやVista以降前提でもいいですよね,ってことで。
楽譜描画。今までウェザタイ同様,DirectDrawで音符の画像を作って描画していたのだが,それだと連符や連桁の斜め線を描画するのが難しい。角度とか…。で,試しにGetDCしてGDIで描画してみたのだが,やっぱり線がギザギザして格好悪い。何かいい方法ないかなあ,ってことでいろいろ探した結果,DirectGraphics 9のID3DXLineを使うことにした。ID3DXLineにはアンチエイリアシング機能があるので良い感じの線を描ける。
で,これを使うにはDirectDrawベースじゃなくてDirect3Dベースにする必要があり大改造なのだが,こんなこともあろうかと描画ロジックを分離して作っていたおかげで2D→3Dへの移行は案外すんなり完了。結局DirectDrawのBltFastを使う描画ロジックとDirect3DのID3DXSpriteを使う描画ロジックを切り替えられるようになった。
これをウェザタイに逆輸入すればウェザタイ3D版ができていろいろなエフェクトが使えるわけだが,今更やろうとは思わないだろうなあ。
最近MIDIファイルの読み込みと再生を実装していたのだが,ウェザタイと同じように,60fpsごとにそれまでに出さなければならなかった音を出すという形にするとかなり重い。これに描画とMIDI inの処理を入れると相当なスペックを要求するソフトになってしまう。普通のMIDIプレイヤーってどうやってるんだろう。
で,MIDIファイル読み込みはだいたいできたので,とりあえずいろんなMIDIファイルを入力してみたが,どうしても解析に失敗するMIDIファイルがある。調べてみるとどうもメタイベントのあと,メタイベントの前のランニングステータスを引き継ぐ前提で作られているらしい。つまり1.MIDIイベント-2.メタイベント-3.MIDIイベントで,3の部分で1のランニングステータスを引き継いでいる。MIDIファイルの規格書を見てみても「メタイベントはランニングステータスをキャンセルするしランニングステータスは適用されない」としか書いてないのでどっちだかいまいち分からない。まあそういうシーケンサがあるんだから対応しろってことだろうけど。
物理の時もそうだったけど,勉強しながら開発するから進みが遅い。もう少し形ができてきたら,音楽に詳しいはっぱさんやまっちーさんに協力を依頼するかな。