ads by Amazon

ラベル 音源モジュール の投稿を表示しています。 すべての投稿を表示
ラベル 音源モジュール の投稿を表示しています。 すべての投稿を表示

2013年4月12日金曜日

16. オペレーション・レコード

この記事では、VGS音源の音源ドライバ・音源モジュール間のオペレーション仕様を示します。

(1)モジュール間I/F
13. 基礎アーキテクチャ」でも解説しましたが、VGSの場合、音源ドライバが音源モジュールに対して、オペレーションを入力し、音源モジュールが発音処理を行います。
音源ドライバ・音源モジュール間のI/F

(2)オペレーション
オペレーションとは、「発音せよ」「消音せよ」といった感じの指令です。
実際、VGSのオペレーションには幾つかの種類がありますが、先ずは最低限必要な発音(KEY-ON)消音(KEY-OFF)待機(WAIT)のオペレーションだけ実装することを目標にします。

(3)WAITオペレーション
楽譜記号のオタマジャクシには、全音符(白い丸)、半音符(白抜きの♩)、四分音符(♩)、八分音符(♪)などの種類があります。その名の通り、半音符は全音符の1/2の長さ、四分音符は全音符の1/4の長さ、八分音符は全音符の1/8の長さという具合です。全音符の長さはテンポにより異なります。
つまり、WAITオペレーションというのは、音や無音(休符)の長さを待機する為のパラメータです。
「それならば、KEY-ONオペレーションに長さのパラメータを持たせれば良いのでは?」と思われるかもしれませんが、「逐次オペレーション」で複数のチャネル(声部)のKEY-ONとKEY-OFFを並列に管理する場合、WAITオペレーションを分離しておいた方が、シンプルに実装できます。(この理屈は、現段階では理解できなくても問題ありません)

(4)音源ドライバの状態遷移
音源ドライバは、下図のような状態遷移をします。
音源ドライバの処理の状態遷移
WAIT以外のオペレーションが発生した場合、連続逐次的にオペレーションの処理を行い、WAITオペレーションが発生した時は待機状態になるようにします。つまり、WAITオペレーションが状態遷移のキーになります。

(5)オペレーション・レコードの設計
それでは、オペレーション・レコードの構造体について考えてみます。
レコード設計をする場合、主キーとそれに関連するプロパティが何かを考えます。
主キーは「オペレーションの種類」です。
そして、オペレーションの種類毎に必要なプロパティ情報(=オペランド)は、次の通りです。
  • Wait: ①時間(周波数と同じとする:1秒=22,050)
  • KEY-ON: ①チャネル番号(0~5)、②音程(0~84)
  • KEY-OFF: ①チャネル番号(0~5)
上記を踏まえて、次のような構造体でオペレーション・レコードを宣言することにします。
/* オペレーションレコード */
#define NOTE_WAIT       0       /* WAIT */
#define NOTE_KEYON      1       /* KEY-ON */
#define NOTE_KEYOFF     2       /* KEY-OFF */
struct _NOTE {
    unsigned char type;         /* 種別 */
    unsigned char op1;          /* オペランド1 */
    unsigned char op2;          /* オペランド2 */
    unsigned char op3;          /* オペランド3 */
    unsigned int val;           /* 値 */
};
256未満で収まるオペランドはop1、op2、op3に持たせ、大きな数値(時間)はvalに持たせる仕様です。なお、1秒=22050で、int=2の32乗-1なので、ひとつの音符または休符の最大長は194,783秒という制約が生じることになりますが、バッソ・コンティヌオでもそんな長い音符はこの世に存在しないので、問題ありません。

15. 蛙の歌を歌わせてみる

前の記事で作成したtoneテーブル(tone.c)のテストを兼ねて、test06.cppをベースにして蛙の歌をBGMとして再生するプログラム(発音テスト)を実装してみることにします。なお、この段階では、まだオペレーションを実装していないので、ダイレクトにテーブルの波形情報をバッファリングする形で実装します。

(1)toneテーブルのextern宣言
まず、tone.cで宣言しているtoneテーブル(TONE1、TONE2、TONE3、TONE4)をextern宣言でアクセスできるようにします。
/* トーンテーブル */ extern "C" { extern short* TONE1[85]; /* 三角波 */ extern short* TONE2[85]; /* ノコギリ波 */ extern short* TONE3[85]; /* 矩形波 */ extern short* TONE4[85]; /* ノイズ */ } 

(2)発音処理の実装
mkbuf関数の、効果音を発音後の部分に、次の発音テストコードを実装します。
/* 音源エミュレータテスト */ { static int hz; static int ply[7]={39,41,43,44,43,41,39}; static int p1; // 発音位置 static int p2; // 波形位置 for(i=0;i<size;i+=2) { bp=(short*)(&buf[i]); wav=*bp; wav+=TONE3[ply[p1]][1+p2]*64; if(32767<wav) wav=32767; else if(wav<-32768) wav=-32768; (*bp)=(short)wav; p2+=2; if(TONE3[ply[p1]][0]<=p2) p2=0; hz++; if(11025<=hz) { p1++; if(7<=p1) p1=0; p2=0; hz=0; } } }

上記のテストコードでは、11025Hzの間隔(=0.5秒間隔)で、「ドレミファミレド」の発音を繰り返しています。別にフルコーラスでも良いのですが、単なるテストコードなので簡単に。なお、TONE3(矩形波)のテーブルを使っていますが、この部分をTONE1にすれば三角波、TONE2にすればノコギリ波、TONE4にすればノイズが奏でられるようになります。

これで実装完了です。
簡単ですね。

この実装を施したtest07.cppはコチラからダウンロードできます。
test07.cppのコンパイルは、次のコマンドでOKです。
CL /MT test07.cpp tone.c user32.lib dsound.lib dxguid.lib

BGM(蛙の歌)を鳴らした状態で、効果音の発音をすることができることも確認してください。
これで、BGMと効果音の両方を鳴らすことができました。

まぁ、ここまでは簡単です。
本格的に難しくなるのは、ここから先です

・・・ここからが地獄だ・・・

恐らく、ここまでの記事では脱落者は少ないものと推測しています。そもそも、このブログで扱っているネタに興味を持たれる時点で、それなりに高いプログラミング能力をお持ちの方が大半だろうと思われるので。しかし、ここから先は、脱落者続出になるかもしれません。

誰も読まないブログ?それもまた一興。

14. toneテーブルの準備

8. PSG音源と波形メモリ音源の違い」で解説したように、これから作成する音源モジュールは、全音ストア方式の音源モジュールです。そこで、楽器毎に全音階分の基礎波形データの音色テーブル(toneテーブル)を準備する必要があります。

(1)テーブル形式
基礎波形データのテーブル形式は、音階毎に1Hz分のパルス符号数と符号付16bit整数のパルス符号を持つ可変長テーブルとします。ごちゃごちゃ言ってますが、要するにshort型配列の塊です。
1Hz分のパルス符号数(n)は、音階によって異なります。
音は、低い音の周期(n1)が長く、高い音の周期(n2)が短い特性があります。
つまり、配列サイズはn2<n1となります。
したがって、可変長配列として準備するのが妥当だと言えます。

(2)テーブル作成作業
それでは、上記の理論を踏まえて、三角波、矩形波、ノコギリ波、ノイズのtoneテーブルを手書きで作成・・・などと愚かなことをやる人は、このブログの読者には居ないと思います。我々プログラマは、そういったバカバカしい作業を効率的にこなすのが得意なので。

私の場合、下記のプログラムを組んで、toneテーブルを一瞬で作成しました。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char* argv[]) { const char *n1[12]={"A","A#","B","C","C#","D","D#","E","F","F#","G","G#"}; const char *n2[12]={"A","AS","B","C","CS","D","DS","E","F","FS","G","GS"}; float cnt[85]={ 55.0000 /* A 0 /00 */ , 58.2705 /* A# 0 /01 */ , 61.7354 /* B 0 /02 */ , 65.4064 /* C 1 /03 */ , 69.2957 /* C# 1 /04 */ , 73.4162 /* D 1 /05 */ , 77.7817 /* D# 1 /06 */ , 82.4069 /* E 1 /07 */ , 87.3071 /* F 1 /08 */ , 92.4986 /* F# 1 /09 */ , 97.9989 /* G 1 /10 */ , 103.8262 /* G# 1 /11 */ , 110.0000 /* A 1 /12 */ , 116.5409 /* A# 1 /13 */ , 123.4798 /* B 1 /14 */ , 130.8128 /* C 2 /15 */ , 138.5913 /* C# 2 /16 */ , 146.8324 /* D 2 /17 */ , 155.5635 /* D# 2 /18 */ , 164.8138 /* E 2 /19 */ , 174.6141 /* F 2 /20 */ , 184.9972 /* F# 2 /21 */ , 195.9977 /* G 2 /22 */ , 207.6523 /* G# 2 /23 */ , 220.0000 /* A 2 /24 */ , 233.0819 /* A# 2 /25 */ , 246.9417 /* B 2 /26 */ , 261.6256 /* C 3 /27 */ , 277.1826 /* C# 3 /28 */ , 293.6648 /* D 3 /29 */ , 311.1270 /* D# 3 /30 */ , 329.6276 /* E 3 /31 */ , 349.2282 /* F 3 /32 */ , 369.9944 /* F# 3 /33 */ , 391.9954 /* G 3 /34 */ , 415.3047 /* G# 3 /35 */ , 440.0000 /* A 3 /36 */ , 466.1638 /* A# 3 /37 */ , 493.8833 /* B 3 /38 */ , 523.2511 /* C 4 /39 */ , 554.3653 /* C# 4 /40 */ , 587.3295 /* D 4 /41 */ , 622.2540 /* D# 4 /42 */ , 659.2551 /* E 4 /43 */ , 698.4565 /* F 4 /44 */ , 739.9888 /* F# 4 /45 */ , 783.9909 /* G 4 /46 */ , 830.6094 /* G# 4 /47 */ , 880.0000 /* A 4 /48 */ , 932.3275 /* A# 4 /49 */ , 987.7666 /* B 4 /50 */ , 1046.5023 /* C 5 /51 */ , 1108.7305 /* C# 5 /52 */ , 1174.6591 /* D 5 /53 */ , 1244.5079 /* D# 5 /54 */ , 1318.5102 /* E 5 /55 */ , 1396.9129 /* F 5 /56 */ , 1479.9777 /* F# 5 /57 */ , 1567.9817 /* G 5 /58 */ , 1661.2188 /* G# 5 /59 */ , 1760.0000 /* A 5 /60 */ , 1864.6550 /* A# 5 /61 */ , 1975.5332 /* B 5 /62 */ , 2093.0045 /* C 6 /63 */ , 2217.4610 /* C# 6 /64 */ , 2349.3181 /* D 6 /65 */ , 2489.0159 /* D# 6 /66 */ , 2637.0205 /* E 6 /67 */ , 2793.8259 /* F 6 /68 */ , 2959.9554 /* F# 6 /69 */ , 3135.9635 /* G 6 /70 */ , 3322.4376 /* G# 6 /71 */ , 3520.0000 /* A 6 /72 */ , 3729.3101 /* A# 6 /73 */ , 3951.3101 /* B 6 /74 */ , 4186.0090 /* C 7 /75 */ , 4434.9221 /* C# 7 /76 */ , 4698.6363 /* D 7 /77 */ , 4978.0317 /* D# 7 /78 */ , 5274.0409 /* E 7 /79 */ , 5587.6517 /* F 7 /80 */ , 5919.9108 /* F# 7 /81 */ , 6271.9270 /* G 7 /82 */ , 6644.8752 /* G# 7 /83 */ , 7040.0000 /* A 7 /84 */ }; float fhz; int hz; int i; int tone=0; float n,m; printf("/*\n"); printf(" *--------------------------------------------------\n"); printf(" * 三角波(SANKAKU)\n"); printf(" *--------------------------------------------------\n"); printf(" */\n"); for(tone=0;tone<85;tone++) { fhz=88200/cnt[tone]; hz=(int)fhz; printf("/* #%02d: tone %s oct %d */\n",tone,n1[tone%12],1+tone/12); m=64.0f / (fhz/4); printf("short TRI_%s%d[%d]={%d",n2[tone%12],1+tone/12,hz+1,hz); for(n=0,i=0;i<hz/4;i++,n+=m) { printf(",%d",(int)n); } for(i=0;i<hz/2;i++,n-=m) { printf(",%d",(int)n); } for(i=0;i<hz/4;i++,n+=m) { printf(",%d",(int)n); } printf("};\n\n"); } printf("short* TONE1[85]={"); for(tone=0;tone<85;tone++) { if(tone)printf(","); printf("TRI_%s%d",n2[tone%12],1+tone/12); } printf("};\n\n"); printf("/*\n"); printf(" *--------------------------------------------------\n"); printf(" * ノコギリ波(NOKOGIR)\n"); printf(" *--------------------------------------------------\n"); printf(" */\n\n"); for(tone=0;tone<85;tone++) { fhz=88200/cnt[tone]; hz=(int)fhz; printf("/* #%02d: tone %s oct %d */\n",tone,n1[tone%12],1+tone/12); m=64.0f / (fhz/2); printf("short NOC_%s%d[%d]={%d",n2[tone%12],1+tone/12,hz+1,hz); for(n=0,i=0;i<hz/2;i++,n+=m) { printf(",%d",(int)n); } for(n=-64.0f;i<hz;i++,n+=m) { printf(",%d",(int)n); } printf("};\n\n"); } printf("short* TONE2[85]={"); for(tone=0;tone<85;tone++) { if(tone)printf(","); printf("NOC_%s%d",n2[tone%12],1+tone/12); } printf("};\n\n"); printf("/*\n"); printf(" *--------------------------------------------------\n"); printf(" * 矩形波(KUKEI)\n"); printf(" *--------------------------------------------------\n"); printf(" */\n\n"); for(tone=0;tone<85;tone++) { fhz=88200/cnt[tone]; hz=(int)fhz; printf("/* #%02d: tone %s oct %d */\n",tone,n1[tone%12],1+tone/12); printf("short KUK_%s%d[%d]={%d",n2[tone%12],1+tone/12,hz+1,hz); for(i=0;i<hz/2;i++) { printf(",%d",48); } for(;i<hz;i++) { printf(",%d",-48); } printf("};\n\n"); } printf("short* TONE3[85]={"); for(tone=0;tone<85;tone++) { if(tone)printf(","); printf("KUK_%s%d",n2[tone%12],1+tone/12); } printf("};\n\n"); printf("/*\n"); printf(" *--------------------------------------------------\n"); printf(" * ノイズ\n"); printf(" *--------------------------------------------------\n"); printf(" */\n\n"); for(tone=0;tone<85;tone++) { fhz=88200/cnt[tone]; hz=(int)fhz; printf("/* #%02d: tone %s oct %d */\n",tone,n1[tone%12],1+tone/12); printf("short NIZ_%s%d[%d]={%d",n2[tone%12],1+tone/12,hz+1,hz); for(i=0;i<hz/2;i++) { printf(",%d",rand()%64); } for(;i<hz;i++) { printf(",%d",-(rand()%64)); } printf("};\n\n"); } printf("short* TONE4[85]={"); for(tone=0;tone<85;tone++) { if(tone)printf(","); printf("NIZ_%s%d",n2[tone%12],1+tone/12); } printf("};\n\n"); return 0; } 
上記プログラムをコンパイルして実行すれば、音色テーブルの配列を宣言するC言語のソースコードが標準出力されるので、それをリダイレクトで保存します。(一種のCGIプログラムです)

保存したファイル(tone.c)はコチラに置いておきます。

(3)tone.c
このソースコードでは、TONE1、TONE2、TONE3、TONE4というshort型配列に、三角波、ノコギリ波、矩形波、ノイズの1Hz×85音階分の配列を宣言しています。各音階配列は、第一要素がパルス符号の数で、第二要素以降がパルス符号データになっています。

(4)補足1
なお、上記の説明を読んでいて、「基礎波形のパルス符号はshort型よりsigned char型の方が良いのでは?」という点に気づけた方が居るとすれば、素晴らしいです。その方がメモリ容量を1/2に節約できるので、品質(効率性)が良くなります。
しかし、波形情報は基本的に16bitで計算するので、基数変換のコストを削るために、short型にしておきました。ついでに、配列の最初の要素にパルス符号数を格納しているので、char型だと収まりきらないという事情があり、short型にしておきました。
でも、結局計算する時は32bitにしている訳だし、後者の件は構造体にすれば問題無いので、signed charの方が良いかもしれません。

(5)補足2
あと、実際の音源モジュールのサンプリング周波数は22050Hzですが、波形データはそれより細かい粒度で作成しています。これは、周波数の誤差を少なくするために必要な処置です。(割り切れない数値が誤差となり、誤差が大きいと若干音痴になってしまうため、基礎波形は粒度を細かくすることで、誤差を少なくしています)