FPGA 版 FM 音源 (55) -- YMF297 (OPN3/OPL3) 測定 (21) -- EG シミュレーション・プログラム (1)
C 言語で書いた「フル機能」の EG シミュレーション・プログラムができました。
詳しい説明は後に回して、今回は結果のグラフとソースのみ掲載します。
(2016 年 12 月 31 日追記: プログラムに RATE = 0 の場合の動作に関するバグがあったのを修正しました)
(2017年 1 月 1 日追記: 上記バグを再修正。 「fs_en」信号を追加。)
(2017年 1 月 11 日追記: 上記とは別の RATE = 0 に関するバグを修正。)
- AR = 15, Rof =3 から AR = 11, Rof = 0 まで変化
- DR は AR と同じ値
- SL = 3 (-9 dB)
- RR = 13 固定
- EGT = 1 (サステイン・タイプ)
- サンプル・インデクス = 1 で KON = 1 (キーオン)
- サンプル・インデクス = 192 で KON = 0 (キーオフ)
の設定で実行した結果のグラフを下に示します。
青いトレースが EG アキュムレータ出力の数値を 8 倍してプロットしたもので、赤いトレースが EG アキュムレータ出力を log—リニア変換した数値をプロットしたものです。
他の設定は上の場合と同一で、EGT のみ
- EGT = 0 (減衰音タイプ)
とした場合の結果を下に示します。
EG シミュレーション・プログラムのソースを下に示します。
Verilog-HDL によるハードウェアでの実現を意識した書き方になっていて、ソフトウェアとして効率の良い書き方にはなっていません。
信号の値が変化するタイミングで計算した値を変数に記憶しておいて使い回しするのではなく、必要な値は毎回計算するようにしています。
// OPL3 EG simulator // 2016/12/30 full function (AR/DR/SL/RR/EGT/Rof) #include <stdio.h> #include <math.h> // state definition for EG state machine typedef enum eg_states { EG_MUTE, EG_ATTACK, EG_DECAY, EG_SUSTAIN, EG_RELEASE } eg_stat_t; // EG accumlator #define EG_ACC_BITS (9) #define EG_ACC_MASK ((1UL << EG_ACC_BITS)-1) // EG acc constant for 0dB, -96dB, -95 dB, -93 dB #define EG_0dB (0x000) #define EG_96dB (EG_ACC_MASK) #define EG_95dB (EG_ACC_MASK-0x3) #define EG_93dB (EG_ACC_MASK-0xf) // rate multiplier lookup table for hi-rate uint8_t rmp_LUT[4][8] = { // psc_cnt[2:0] // 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 }; // uint8_t rmp_LUT // Rof to prescaler bitmask for lo-rate uint8_t rof_mask[4] = { 0x01, // 3'b001 for Rof = 0 0x05, // 3'b101 for Rof = 1 0x03, // 3'b011 for Rof = 2 0x07 // 3'b111 for Rof = 3 }; // uint8_t rof_mask[] uint16_t lb2lin_tab[256]; int ar, dr, sl, rr, rof, egt; uint16_t psc_cnt, psc_z1; eg_stat_t eg_stat; // // 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() // Calculate prescaler clock enable by rate multiplier uint16_t calc_psc_en(int fs_en, int rate, int rof) { uint16_t psc_en; if (fs_en) { // prescaler update timing psc_z1 = psc_cnt++; } // if (fs_en) { ... psc_en = (~psc_z1 & psc_cnt); return(0 != (rof_mask[rof] & (psc_en >> (12 - rate)))); } // uint16_t calc_psc_en(); int EG_module(uint16_t *eg_mem_p, int fs_en, int kon_rise, int kon_fall, int egt, int ar, int dr, int sl, int rr, int rof) { // WIREs int rate, rof10, hi_rate; int ar15, rate15, rate13, rate0; int eq_0dB, eq_95dB, eq_slx; int trans; int sft_sel, clk_en, sft, slx; int acc_clk_en; int A, D, S, R, M; int to_A, to_D, to_S, to_R, to_M; uint16_t lin_out, eg_add; int16_t eg_inc; // REGs uint16_t eg_acc; uint8_t eg_stat; // unpack EG_state / EG_acc from slot memory eg_acc = (EG_ACC_MASK & (*eg_mem_p)); eg_stat = ((*eg_mem_p) >> EG_ACC_BITS); // // combinational signal (WIREs) calculation // // decode SL ( 0dB..-42dB for SL=0..14, -93dB for SL=15) slx = (sl << 4) + (0x100 * (15 == sl)); // EG state decode A = (EG_ATTACK == eg_stat); D = (EG_DECAY == eg_stat); S = (EG_SUSTAIN == eg_stat); R = (EG_RELEASE == eg_stat); M = !(A | D | S | R); // rate MUX rate = (A ? ar : 0); rate |= (D ? dr : 0); rate |= (R ? rr : 0); rate0 = (0 == rate); // actual_rate = rate_reg_value + rate_offset if (rate0) { rof = 0; // force zero offset } rof10 = (0x03 & rof); // LS 2 bits of Rof rate += (rof >> 2); // MS 2 bits of Rof if (15 <= rate) { rate = 15; // clip to 15 rof10 = 0; // no fraction } // rate flags hi_rate = (13 <= rate); rate15 = (15 == rate); rate13 = (13 == rate); ar15 = (A & rate15); // eg_acc value comparator eq_0dB = (EG_0dB == eg_acc); // attack peak (0dB) eq_95dB = !(EG_95dB & (EG_95dB ^ eg_acc)); // decay / release bottom (-95dB) eq_slx = !(EG_93dB & (slx ^ eg_acc)); // crossing sustain level // state machine transition flag to_A = (!A & kon_rise); // to attack to_D = ( A & eq_0dB); // attack to decay to_S = ( D & eq_slx); // decay to sustain to_R = (!M & !R & kon_fall); // to release to_M = ((D | R) & eq_95dB); // decay/release to mute trans = (to_A | to_D | to_S | to_R | to_M); // EG state transition // clock prescaler and rate multiplier clk_en = (!rate0) * calc_psc_en(fs_en, rate, rof10); clk_en |= hi_rate; // continuous clock for hi-rate // calculate shift amount sft_sel = rmp_LUT[rof10][0x07 & psc_cnt]; // table lookup sft_sel |= rate15; // always '1' for rate = 15 sft_sel &= hi_rate; // always '0' for lo-rate sft = (!sft_sel + (!hi_rate | rate13)); // inhibit EG acc update at EG state transition timing acc_clk_en = (M | (!trans & clk_en)); eg_inc = (A ? (~eg_acc >> 1) : 0) // attack : 1's compl. of EG acc | ((D | R) ? 0x04 : 0) // decay/release : fixed pattern ; eg_inc >>= sft; // barrel shift with sign eg_add = (eg_acc + eg_inc); // adder output // // EG accumulator update (REG) // if (acc_clk_en) { // EG acc update timing if (M) { // mute state eg_acc = EG_96dB; // force mute } else if (!ar15) { eg_acc = eg_add; // normal EG acc operation } else { // AR = 15 eg_acc = 0; // force immediate peak } // if (M) { ... } // if (acc_clk_en) { ... eg_acc &= EG_ACC_MASK; // limit to 9 bit width // // EG state machine update (REG) // if (trans) { // EG state trasition timing if (to_A) { eg_stat = EG_ATTACK; } else if (to_R) { eg_stat = EG_RELEASE; } else if (to_D) { eg_stat = EG_DECAY; } else if (to_S) { eg_stat = (egt ? EG_SUSTAIN : EG_RELEASE); } else if (to_M) { eg_stat = EG_MUTE; } // if (to_A) {} else { ... } // if (trans) { ... // pack and save updated EG_state / EG_acc to slot memory *eg_mem_p = (((uint16_t)eg_stat << EG_ACC_BITS) | eg_acc); return(eg_acc); } // int EG_module_v2() void main( void ) { int i, eg_out, lin_out; int psci; uint16_t eg_mem[1] = {EG_96dB}; // generate lb to lin table gen_lb2lin_tab(); sl = 3; // -9 dB rr = 13; egt = 1; // EG type = sustain for (ar = 15; ar >= 11; ar--) { dr = ar; for (rof = 3; rof >= 0; rof--) { for (psci = 0; psci < 1; psci++) { fprintf(stdout, "# DR=%d, Rof=%d, psc_ini=%d\r\xa", dr, rof, psci); psc_cnt = psci; // prescaler counter initial value for (i = 0; i < 512; i++) { printf("%5d", i); eg_out = EG_module(eg_mem, 1, (1 == i), (192 == i), egt, ar, dr, sl, rr, rof); // convert lb to linear value lin_out = lb2lin(0, (eg_out << 3)); printf(" %4d %3d", lin_out, eg_out); printf("\r\xa"); } // for (i = 0; ... // 2 empty lines for GNUPLOT data block separator printf("\r\xa\r\xa"); } // for (psci = 0; ... } // for (rof = 3; ... } // for (ar = 15; ... } // void main()