FPGA 版 FM 音源 (21) -- YMF262 測定 (13)
アタックからディケイへ変化するだけの、単純なエンベロープを発生する C プログラムのリストを下に示します。
アタック・レート (AR) を 15 から 11 まで、Rof を 0 から 3 まで変えながら、それぞれの設定のアタック・ディケイ・カーブを EG アキュムレータの値が 511 に到達するまで、各行に、サンプル・インデクス、リニア出力値、EG アキュムレータ値、の順で出力します。
// OPL3 EG (AR/DR) simulator // 2011/02/02 #include <stdio.h> #include <math.h> typedef enum eg_states { EG_OFF, EG_AINIT, EG_ATTACK, EG_A2D, EG_DECAY, EG_SUSTAIN, EG_RELEASE } eg_stat_t; // prescaler carry bit mask #define PSC_CMASK (0x1000) // EG acc constant for 0dB, -96dB #define EG_0dB (0x000) #define EG_96dB (0x1ff) // rate multiplier / shift amount modulator // look-up table typedef int rmp_LUT_t[8]; rmp_LUT_t rmp_LUT[4] = { // rmp_cnt // 0 1 2 3 4 5 6 7 {0, 0, 0, 0, 0, 0, 0, 0}, // Rof = 0 {0, 0, 0, 0, 0, 0, 1, 1}, // Rof = 1 {1, 1, 0, 0, 1, 1, 0, 0}, // Rof = 2 {1, 1, 0, 0, 1, 1, 1, 1}, // Rof = 3 }; // rmp_LUT_t sft_LUT int lb2lin_tab[256]; int eg_acc, rate, rof10, rmp_cnt; int psc_acc, psc_inc; int hi_rate_flag, sft_base; eg_stat_t eg_stat; int ar, dr, rof; // // lb-to-lin conversion with sign // int lb2lin(int sign, int lb) { int lin, sft; sft = (lb / 256); // integer part of lb value lb = (lb % 256); // fraction part of lb value // force 0 if too small lin = ( (15 < sft) ? 0 : (lb2lin_tab[lb] >> sft) ); return((-sign) ^ lin ); // 1's complementer } // int lb2lin() // // generate lb-to-lin table for 8 bit fraction part of lb // void gen_lb2lin_tab( void ) { int i; double v; for (i = 0; i < 256; i++) { // for 8 bit fraction // (2 ** (-x)) = exp(-log(2) * x) v = 2048 * exp(-log(2) * (i + 1) / 256.0); lb2lin_tab[i] = (int)(v + 0.5) * 2; } // for (i = 0; ... } // void gen_lb2lin_tab() // EG rate setup void EG_set_rate(int rate_ini, int rof_ini) { rate = (rate_ini * 4) + rof_ini; // initial rate value rof10 = 0x03 & rate; // LS 2 bits rate /= 4; // MS 4 bits if (0 == rate) { rof10 = 0;} if (15 <= rate) { // clip to 15 rate = 15; rof10 = 0; // no fractions } hi_rate_flag = (12 < rate); sft_base = 3 - (13 < rate); psc_acc = 0; // reset prescaler if (hi_rate_flag) { // for hi-rate psc_inc = 0; // no prescaler update } else { // for lo-rate, set prescaler incr. psc_inc = (1 << ((0x0f & rate) )); } } // void EG_set_rate() // EG initialize void EG_init( void ) { EG_set_rate(0, 0); // rate = 0 for no acc update eg_acc = EG_96dB; // minimum level rmp_cnt = 0; // prescaler counter eg_stat = EG_OFF; // set to OFF state } // void EG_init() int EG_module(int key_stat) { int sft_sel, clk_en, sft; clk_en = hi_rate_flag; // continuous clock for hi-rate sft = sft_base; // fixed EG acc increment for lo-rate // clock prescaler and rate multiplier psc_acc += psc_inc; // increment prescaler acc // test for prescaler carry if (hi_rate_flag | (PSC_CMASK & psc_acc) ) { psc_acc &= (~PSC_CMASK); // clear prescaler carry // rate multiplier / shift amount modulator sft_sel = rmp_LUT[rof10][rmp_cnt]; // table lookup sft_sel |= (15 == rate); // always '1' for rate = 15 if (hi_rate_flag) { // for hi-rate sft = sft_base - sft_sel; // shift amount mod. } else { // for lo-rate, clock rate modulation clk_en = (sft_sel | (0 == (0x01 & rmp_cnt))); } // if (hi_rate_flag) ... // update rate multiplier counter rmp_cnt = 0x07 & (rmp_cnt + 1); } // if (hi_rate_flag | ... // EG accumulator update if (clk_en | (EG_A2D == eg_stat) | (EG_AINIT == eg_stat)) { switch (eg_stat) { case EG_AINIT: // init for ATTACK EG_set_rate(ar, rof); // set attack rate and init psc eg_stat++; // advance to next state break; case EG_ATTACK: // if rate = 15, attack time is "0" if (15 == rate) { eg_acc = EG_0dB; } if (EG_0dB == eg_acc) { // is it 0 dB? eg_stat++; // advance to next state } else { // add 1's complement of 1/2/3-bit right shifted eg_acc if (0 < rate) { eg_acc += (~(eg_acc >> sft)); } } // if (0 == eg_acc) ... break; case EG_A2D: // ATTACK to DECAY transition EG_set_rate(dr, rof); // set decay rate and init psc eg_stat++; // advance to next state break; case EG_DECAY: case EG_RELEASE: // increment EG acc by 1, 2 or 4 if (0 < rate) { eg_acc += (8 >> sft); } if (EG_96dB <= eg_acc) { // EG OFF? eg_acc = EG_96dB; eg_stat = EG_OFF; } // if (EG_96dB <= eg_acc) break; case EG_OFF: // no EG update break; default: break; } // switch (eg_stat) { ... } // if (clk_en) ... return(eg_acc); } // int EG_module() void main( void ) { int i, eg_out, op_out; gen_lb2lin_tab(); for (ar = 15; ar > 10; ar--) { dr = ar; for (rof = 3; rof >= 0; rof--) { EG_init(); // initialize EG vars eg_stat = EG_AINIT; for (i = 0; EG_OFF != eg_stat ; i++) { eg_out = EG_module(1); // compute EG // convert lb to linear value op_out = lb2lin(0, (eg_out << 3)); printf("%5d %5d %5d \r\xa", i, op_out, eg_out); } // for (i = 0; ... // 2 empty lines for GNUPLOT data block separator printf("\r\xa\r\xa"); } // for (rof = 3; ... } // for (ar = 13; ... } // void main()
アタック → ディケイの遷移部分については、まだ、実際の値と十分な一致が得られていません。
各エンベロープ・カーブの間には空行を 2 行挿入してあり、GNUPLOT で「データ・ブロック」の区切りとして認識されます。
レート・マルチプライアの出力ロジック部は、ソフトウェアによる実現なので、ビットごとの and / or では構成せずに、「テーブル・ルックアップ」で求めています。