FM音源プログラム (33) -- データ構造

オペレータを組み合わせて構成した、1音の「発音単位」のことを、ここでは「ユニット」(unit) と呼ぶことにします。
FM音源チップでは「チャンネル」と呼ばれており、一般的な音源の用語としては「ボイス」になります。
1音に複数の発音機構を割り当て、複数の音色を重ねることを「レイヤード・ボイス」と言ったりします。
MIDI チャンネルごとの設定情報も含め、オペレータ/ユニット/チャンネル単位でデータ構造をまとめていますので、その説明をしたいと思います。

オペレータ・プロパティ

まず、オペレータですが、前に説明したように、主にピッチ関係の変数と、EG 関係の変数で構成されています。
「オペレータ・プロパティ・タイプ」の意で、op_prop_t という構造体の型を定義してあります。

/* operator property definition */

typedef struct {
    uint32_t  phacc;   // phase accumulator
    uint32_t  ph_inc;  // phase increment (pitch)
    int32_t   mod_in;  // phase modulation input
    int32_t   egacc;   // EG accumulator
    int32_t   attacc;  // EG attack accumulator
    int32_t   eginc;   // EG acc increment
    int32_t   eginit;  // EG acc init value
    int32_t   ol_lin;  // output level in lin
    int32_t   tl_lin;  // total level in linear
    int32_t   sl_lin;  // sustail level in linear
    int32_t   tl_lb;   // TL + KSL + VEL in log2
    int32_t   sl_lb;   // sustain level in log2
    int32_t   vol_lb;  // VOL + EXP in log2
    int32_t   am_lb;   // AM in log2
    int16_t   egrate;  // EG rate
    int16_t   opout;   // wave output of this op
    op_vparam_t vp;    // voice parameters
    uint8_t  egmode;   // EG mode
    uint8_t  last_egm; // last EG mode
    uint8_t  rof;      // KSR offset (0..15)
    int8_t   gate_q;   // key-ON gate queue
    int8_t   damp_q;   // damper queue
  } op_prop_t;

これは、ADuC7026 版、V850 版の 32 ビット RISC マイコンの場合の定義で、8 ビット変数で用が足りる場合でも、アクセスの高速化のために 32 ビット変数を主に使用しています。
8 ビットや 16 ビット変数を使うと RAM は節約できますが、32 ビット・レジスタへのゼロ拡張や符号拡張の命令が必要になり、動作速度の点では不利になります。
通常は、オペレータ・プロパティへのポインタを渡しますから、

void func(op_prop_t *op_p)
{
   ...
   op_p->phacc += op_p->ph_inc;
   ...
}

のように、ポインタを利用して構造体メンバを参照する場合の記法の「->」を使って各メンバにアクセスします。
phacc, ph_inc, mod_in は、ピッチ/位相変調関係の変数です。
オペレータ関数をアセンブラで書く場合にオフセット値が求めやすいように、構造体先頭から配置してあります。
egacc から egrate までと egmode から damp_q までは EG 関係の変数です。
変数がリニア値の場合には、変数名の末尾に「_lin」を、lb 値の場合には「_lb」を付けてあります。
オペレータの音色パラメタは、下に示すような構造体のメンバに格納して利用しています。
ROM 上のパックされたデータからの抽出/展開は、MIDI メッセージのプログラム・チェンジを受け取ったタイミングで行っています。

/* operator decoded voice parameter structure */

typedef struct op_vparam_st {
  uint8_t  ksr;     // key scaling rate
  uint8_t  egt;     // EG type         
  uint8_t  vib;     // vibrato flag   
  uint8_t  mult;    // freq multiplier (1..30) 
  uint8_t  dr;      // EG delay  rate 
  uint8_t  rr;      // EG release rate
  uint16_t sl;      // EG sustain level
  uint8_t  ar;      // EG attack rate
  uint8_t  ksl;     // key scaling level
  uint16_t tl;      // EG total   level
  uint8_t  wavesel; // wave select    
  uint8_t  am;      // AM flag        
  uint8_t  dam;     // AM depth       
  uint8_t  dvb;     // vibrato depth  
  uint8_t  carrf;   // carrier flag 
  uint8_t  op_num;  // Operator number
  int16_t  (*op_func_p)(void *o_p);
} op_vparam_t, *op_vparam_p;
ユニット・プロパティ

ユニット・プロパティ・タイプは次のように定義されています。

/* unit propery definition */

typedef struct {
    uint32_t ph_inc;    // f-number in linear format
    int32_t  note_f;    // current note number with fraction
    int32_t  porta_delta; // portamento acc increment (signed)
    int32_t  m_note_f;  // target note number with fraction
    int      waveout;   // waveform output of this unit
    int      vel_lb;    // MIDI velocity   in log2
    int      exp_lb;    // MIDI ch vol * MIDI expression in log2
    int16_t  ks6_0dB;   // Level key scaling value (6.0dB/oct)
    uint8_t  on_cnt;    // multiple keyon counter
    uint8_t  midi_note; // MIDI note number (0..127)
    uint8_t  midi_vel;  // MIDI velocity    (0..127)
    uint8_t  oct;       // effective octave   (0..10)
    uint8_t  keycode;   // effective keycode  (0..11)
    int8_t   gate;      // key-ON gate flag
    int8_t   damper;    // Hold1/Damper flag
    int8_t   porta_flag;// portamento flag
    uint8_t  hold;      // CC#64 hold1/dumper flag (0..1)
    uint8_t  ksn;       // key scaling number
    uint8_t  fb;        // modulator feedback depth 
    uint8_t  lfo;       // LFO setting      
    uint8_t  alg;       // algorithm
    uint8_t  midi_ch;   // MIDI ch assigned to this unit  
    unit_vparam_t uv;   // unit voice parameter (fb/LFO/alg)
    op_prop_t op_prop[2]; // property for operator 0, 1
    int      voice_param_change; // voice parameter change flag
    int      LFO_param_change;   // LFO parameter change flag
  } unit_prop_t;

LFO 周波数やフィードバック、アルゴリズムはユニットのプロパティとして扱っています。
1 ユニット 2 オペレータ構成は変化しないので、ユニット・プロパティのメンバとして、オペレータ・プロパティの実体を置いています。

チャンネル・プロパティ

チャンネル・プロパティ・タイプを次に示します。
ここで、「チャンネル」と言っていますが、「パート」と言った方が正確です。
当初は、同時発音数 3 程度で、MIDI チャンネルの割り当ても固定だったので、「チャンネル」としたのですが、現在は MIDI チャンネル割り当て変更機能も実装したので、「チャンネル」では実状に合わなくなっています。
ここには MIDI チャンネルごとの設定情報を集めてあります。
プログラム・チェンジで選ばれた音色データの ROM 上の表現そのもののコピー、およびそれを展開したデータの両者を保持しています。
発音ユニットへの音色データ・ロードは、展開ずみ音色データをユニット・プロパティの音色データ部へブロック転送するだけです。
FM音源チップでは、チップ全体で LFO はひとつですが、チャンネルごとに LFO を設けた方がロジックとしては簡単になるので、FM音源プログラムでは、LFO はチャンネルのプロパティとしています。

/* MIDI channel (part) property */

typedef struct {
     int32_t bend_f;    // pitch bend (b31..b16:int, b15..b0:frac)
     int32_t tune_f;    // ch tune    (b31..b16:int, b15..b0:frac)
     int32_t porta_inc; // portamento increment
     int32_t vLFO_f;    // vibrato LFO output in fractional note format
    uint16_t aLFO;      // AM (tremolo) LFO output in log2 format
     int16_t waveout;   // wave output
     int16_t vol_lb;    // MIDI volume     in log2
     int16_t exp_lb;    // MIDI expression in log2
     int16_t MD_bend;   // MIDI pitch bend (-8196..8195)
     int16_t coarse_tune; // coarse tune (-64..63)
     int16_t fine_tune;   // fine   tune (-255..255)
    uint      LFO_ph_acc; // LFO phase accumulator
    uint      LFO_ph_inc; // LFO phase acc increment
    uint16_t MD_RPN_ML;   // combined  RPN MSB:LSB (0x0000..0x7f7f)
    uint16_t MD_NRPN_ML;  // combined NRPN MSB:LSB (0x0000..0x7f7f)
    uint16_t MD_mod_range;  // MIDI RPN#5
    uint8_t  MD_bend_range; // MIDI RPN#0
    uint8_t  MD_coarse_tune;// MIDI RPN#1
    uint8_t  MD_fine_tune;  // MIDI RPN#2
    uint8_t  MD_progno;     // MIDI program number
    uint8_t  MD_note;       // MIDI note number
    uint8_t  MD_velocity;   // MIDI velocity
    uint8_t  MD_modulation; // MIDI CC#1
    uint8_t  MD_porta_time; // MIDI CC#5
    uint8_t  MD_volume;     // MIDI CC#7
    uint8_t  MD_pan;        // MIDI CC#10
    uint8_t  MD_expression; // MIDI CC#11
    uint8_t  MD_hold1;      // MIDI CC#64
    uint8_t  MD_porta_sw;   // MIDI CC#65
    uint8_t  MD_legato;     // MIDI CC#68
     int8_t  MD_timbre;     // MIDI CC#71
     int8_t  MD_brightness; // MIDI CC#74
     int8_t  MD_vib_rate;   // MIDI CC#76
     int8_t  MD_vib_depth;  // MIDI CC#77
     int8_t  MD_vib_delay;  // MIDI CC#78
    uint8_t  MD_porta_src;  // MIDI CC#84
    uint8_t  MD_rev_send;   // MIDI CC#91
    uint8_t  MD_dent_MSB;   // MIDI CC#6
    uint8_t  MD_dent_LSB;   // MIDI CC#38
    uint8_t  LFO_rate;      // 2-bit coded vibrato rate
    uint8_t  LFO_depth;     // 2-bit coded vibrato depth
    uint8_t  last_on_note;  // MIDI note number of most recently ON
    uint8_t  last_off_note; // MIDI note number of most recently OFF
    uint8_t  midi_ch;       // MIDI ch number  
    uint8_t  in_use[1];     // assigned unit number table 
     int8_t  so_tune_ofs[12];  // MIDI scale/octave tuning offset
    op_vparam_t ov[2];      // operator voice parameters
    unit_vparam_t uv;       // unit voice parameters
    struct voicedata vdat;  // MA-2 format voice data
  } ch_prop_t;