ads by Amazon

Google+ Badge

2013年4月12日金曜日

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ですが、波形データはそれより細かい粒度で作成しています。これは、周波数の誤差を少なくするために必要な処置です。(割り切れない数値が誤差となり、誤差が大きいと若干音痴になってしまうため、基礎波形は粒度を細かくすることで、誤差を少なくしています)

0 件のコメント:

コメントを投稿