objective-cをちょい研究!ブログ -42ページ目

objective-cをちょい研究!ブログ

WILLCOMを卒業してiOS開発者に転身しました。。
コメント&トラックバック大歓迎です。

なんとか方形波による音色再生はできるようになったので、今度は音色を付けたいってことで、TiMidity用の音色データを調べてみました。


TiMidityの音色データは、Gravis Ultrasound(GUS)というサウンドボード用のパッチファイル(.pat)を使っているようです。

http://www.linux.or.jp/JF/JFdocs/Sound-Playing-HOWTO-2.html
2. 各種音声フォーマットの再生(The Linux Sound Playing HOWTO様)


バイナリエディタとgspmidiのソースより、patファイルのフォーマットを調べて見ました。

※2byte以上の数値情報は、リトルエンディアン(下位バイト→上位バイトの順に格納)です。


<全体の流れ>

000H~ ヘッダーブロック[239byte]
0EFH~ 音色情報ブロック[96byte]
14FH~ 音色データブロック(最後まで)


<ヘッダーブロック>
ファイル情報がテキストで書かれている。

以下の情報が\0区切りで格納されている。


ファイル識別とバージョン
 GF1PATCH110
ID
 ID#000002
著作権情報
 Copyright 1992,1993 EYE&I Productions and Advanced Gravis\1A


<音色情報ブロック>

0EFH 音色名[7byte]
0F6H 分数?[1byte] (上位4bit=ループ開始用/下位4bit=ループ終了用)
0F7H 音色データブロックサイズ[4byte]
0FBH ループ開始[4byte]
0FFH ループ終了[4byte]
103H サンプリングレート[2byte]
105H Low周波数[4byte]
109H High周波数[4byte]
10DH Root周波数[4byte]
111H 未使用(?)[2byte]
113H パン[1byte] (0~15の16段階)
114H エンベロープ[12byte]
120H トレモロ[3byte]
123H ビブラート[3byte]
126H モデルビット情報[1byte]
 (※gspmidiのinstrum.hより
  0bit=16BIT/1bit=UNSIGNED/2bit=LOOPING
  3bit=PINGPONG/4bit=REVERSE/5bit=SUSTAIN
  6bit=ENVELOPE)

127H 未使用(オプション)[28byte]


<音色データブロック>

PCM形式だと思うんだけど、調査中。

WAVE波形を生成してSoundAPIで音が鳴るようにしてみました。
アドエスメロディキーボードではWM6のMIDI再生機能を使っていたので、アドエスでしか使えなかったんですが、従来からのSoundAPIを使っているのでエスでも音が鳴るようになりました。


1~9キーで、ド~ド~レの音が鳴ります。
SoundAPIの部分だけですが、ソース付きです。
http://www.ne.jp/asahi/phs/phs/blog/midi080309.lzh


基本的には、次の3つの関数で再生させてます。
waveOutOpen
waveOutPrepareHeader
waveOutWrite


waveOutWriteはWAVEブロックデータを渡して音を鳴らすんですが、複数渡すと再生予約されて順番に鳴らしてくれます。
リアルタイムに音を鳴らすために1ブロックを短い音(24ms)に分割してストリーミング再生させています。
キーが押された場合、今再生中の次のブロックから音階データを書き換えるという具合。


<課題1 音が鳴るまでのタイムラグ>
ブロックデータを順番に再生させているので、どうしてもタイムラグが出てしまいます。1ブロックを短くしすぎると、データ生成が追いつかず音が途切れてしまう…。
waveOutWriteで再生予約後でもデータの書き換えができるようなので、キーが押された場合は随時データを書き換えることでだいぶタイムラグが改善されたと思う。


<課題2 音階データを作る>
周波数が440Hzの音を「ラ」として、周波数が2倍するごとに1オクターブ上がるそうです。
計算するのは面倒そうなので、周波数対応表を見つけたので利用させてもらいました。


http://homepage1.nifty.com/toshio-k/inspi/interval.html
音程について(KID's World様)

http://www.yk.rim.or.jp/~kamide/music/notes.html
音階と周波数の対応表(JavaScript)(Kamide's page様)

GSPlayerのMIDIプラグインですが、MIDIファイル名を渡してあげるとWAVEデータとして返してくれるようです。
そのデータをSoundAPIのwaveOutWriteに渡してあげればよいみたい。


<簡単な流れ>(SoundAPIは青プラグインは紫)

//プラグインを読込む
pPlugin = pmapGetDecoder()


//プラグイン初期化
pPlugin->Init()


//MIDIファイルを開いて、ファイル情報受取り
pPlugin->PlugInOpenFile(MIDIファイル名, &ファイル情報)

 //ファイル情報
 //.nChannels    チャンネル数
 //.nSampleRate   サンプリングレート(Hz)
 //.nBitsPerSample サンプリングあたりのビット数(bit/Hz)


waveOutOpen(引数…)     //オーディオデバイスのオープン
waveOutPrepareHeader(引数…) //WAVEデータの準備


//MIDIデータの変換開始
pPlugin->StartDecodeFile()


//MIDIデータのWAVEデータを受け取る
pPlugin->DecodeFile(WAVEHDRバッファ)


waveOutWrite(引数…) //WAVEデータを再生


http://www13.plala.or.jp/kymats/study/MULTIMEDIA/waveOut_create.html
WAVEデータの作成と再生(Windowsプログラミング研究所様)


この方法でMIDIを鳴らすことはできそうだけど、プラグインにはMIDIメッセージではなくてMIDIファイルを渡すので、メロディキーボードやLED連動再生みたいなリアルタイムな再生させるのは難しそう。
まずは、SoundAPI側の勉強がてらに、Zero3WAVEキーボードでも作ってみるかな。
(WAVE鳴らすだけならPlaySound関数でも十分だけど…)

GSPlayerのMIDIプラグイン用DLLを外部参照してMIDIが再生できないか調べて
いたんですが、実際に音を出しているのはGSPlayer本体のようで、DLLをコール
して簡単に音を鳴らすなんてわけにはいかないようです(^^;


http://www.ameblo.jp./willcom-phs/entry-10071175274.html
(2/8 Zero3でMIDI再生するソフト(GSPlayer))


GSPlayer本体のソースも公開されているので、こちらも調べてみました。
再生部分はライブラリ(LIB)化していて、本体からLIBを呼出すようになっていて
ました。このLIBを拝借して、音が鳴らせるかの調査。


<ライブラリファイルを作る>
1.GSPlayerのソース(gsp228src.zip)を落とします。
http://hp.vector.co.jp/authors/VA032810/
(GreenSoftware様)


2.次の3つのプロジェクトをそれぞれVisual Studioで呼出してビルドします。

libmad\libmad.vcproj
libovd\libovd.vcproj
maplay\maplay.vcproj


3.lib\ARMV4Dbgフォルダにライブラリファイルが生成されます。

libmad.lib
libovd.lib
maplay.lib


<ライブラリを読み込む>
自作のC++プログラムに生成したライブラリを組み込みます。


1.作ったライブラリをC++ソースと同じフォルダにコピーします。
2.ライブラリの参照設定します。

ソリューションエクスプローラのプロジェクトを右クリック
プロパティ→構成プロパティ→リンカ→入力
[追加の依存ファイル]欄に下記を貼り付け

winsock.lib libmad.lib libovd.lib maplay.lib


3.GSPlayerのincludeフォルダにあるmaplay.h mapplugin.h をソースと同じフォルダにコピー。


4.ソースから呼出してみる

#include "maplay.h"


HANDLE hLib;
hLib = MAP_Initialize(); //初期化


BOOL b1 = MAP_Open(hLib, _T("\\My Documents\\test.mid")); //MIDIファイルを読込む
BOOL b2 = MAP_Play(hLib); //MIDIファイル再生


//b1,b2は、1が帰ってくれば成功、0が帰ってくれば失敗


あらかじめGSPlayerでMIDIが鳴るようにインストールしといてください。
作ったアプリと同じフォルダにgspmidi.dllをおけばMIDIが鳴るはずです。

バージョンアップの課題と調査用バージョンの公開です。


<課題1 画面タッチ>
画面にピアノ鍵盤画像を貼り付けて、画面タッチで音を出せるようにする。
画面タッチイベントは、マウスと同様にMouseDown/MouseMove/MouseUpイベントが使えました。


<課題2 キー入力>
*キーや#キーを押すと、なぜかキーダウンイベントが2回発生してしまう。
KeyDownイベントのe.KeyCodeでどのキーかを判定してるんですが、#キーと3キーで同じe.KeyCodeが渡されるので、e.KeyDataでもチェックする必要がある。


3キー押下時
KeyCode=51 KeyData=51


#キー押下時
KeyCode=51 KeyData=65587 (1回目)
KeyCode=16 KeyData=16   (2回目)


あと、同時に4つ以上のキーを押すと、4つ目以降のキー押下イベントが来ないようです。
また、3つキーを押している時に4つ目のキーを押すと、押していた3つのキーアップイベントが来て、4つ目のキーを離すと、また3つのキーのキーダウンイベントが来るようです???

同じことをZero3のメモ帳でやるとわかると思います。


<調査用バージョンの公開>
キーボードとテンキー、画面タッチで音が出るようにしました。
調査用バージョンってことで、キー入力と画面タッチのイベント情報や、鳴動ノード番号が画面に表示されます。
メニューでオクターブを切り替えられるようにしたんですが、まだガード機能をつけてません。音を鳴らしながらメニュー操作や縦横切替すると音が止まらなくなります(^^; アプリを終了させるか、同じ音階のキーをもう一度押せば消えます。
キーボードを開いたときは、ダイアルキーで低音が鳴るようにしてみたんですが、ノード60以下だとまともな音階で鳴りません(--;


http://www.ne.jp/asahi/phs/phs/blog/midi080224.lzh