EG サンプル↓

LFO と EG は高い頻度で動かす必要がない(と思った)ので、↓のように、1/16 の頻度に落としてCPUを節約している。(あと負荷を分散させるよう工夫もしている)
ただ、LFO もサンプリングレートで動かしてやれば、2オペレータのFM音源にもなるかも知れない。(参考:JO-MIDI-FM)
今は、VCFをどう入れられるかが興味の対象なのだが、一段落したらFM音源化にもチャレンジしてみたい。
static void generate_snd() {
static uint8_t cnt;
:
cnt++;
if (cnt >= 16) {
do_lfo();
cnt = 0;
} else if (cnt == 8) {
do_eg();
}
}
LFO の本体はこんな具合。VCO を 正弦波専用に単純化して、VCOのパラメータを変更するコード。
static void do_lfo() {
uint8_t pos;
int8_t rev = 0;
int8_t v;
int16_t v2;
lfo.wave_pos += lfo.wave_inc;
pos = lfo.wave_pos >> 8;
if (pos >= 128) {
rev = 1;
pos = 255 - pos;
}
if (pos >= 64) {
pos = 127 - pos;
}
v = __LPM(sintab+pos);
if (rev) v = -v;
v2 = (int16_t)v * lfo.intensity;
vco.lfo_inc = fmulsu16(v2, vco.wave_inc)>>4;
}
EG はちょっと長いけれども、全部載せるとこんなかんじ。
static void do_eg() {
uint8_t state = eg.state;
if (state == EG_NOTEOFF) {
eg.intval = (0 << 8);
}
if (state == EG_NOTEON) {
eg.sustain_cnt = 0;
if (eg.attack == 0) {
eg.intval = eg.sustain_lvl;
eg.state = state = EG_SUSTAIN;
} else {
eg.intval = 0;
eg.state = state = EG_ATTACK;
}
}
if (state == EG_ATTACK) {
if ((uint32_t)eg.intval + eg.attack >=
(255<<8)) { // overflow
eg.intval = (255 << 8);
if (eg.sustain_lvl == 0) {
eg.state = EG_RELEASE;
} else {
eg.state = EG_DECAY;
}
} else {
eg.intval += eg.attack;
}
} else if (state == EG_DECAY) {
eg.intval = fmul16(eg.intval, eg.decay);
if (eg.intval <= eg.sustain_lvl) {
eg.intval = eg.sustain_lvl;
if (eg.sustain_time == 0) {
eg.state = EG_RELEASE;
} else {
eg.state = EG_SUSTAIN;
}
}
} else if (state == EG_SUSTAIN) {
eg.sustain_cnt ++;
if (eg.sustain_cnt == eg.sustain_time) {
eg.state = EG_RELEASE;
}
} else if (state == EG_RELEASE) {
eg.intval = fmul16(eg.intval, eg.release);
if (eg.intval < (1 << 8)) {
eg.intval = 0;
eg.state = EG_NOTEOFF;
}
}
eg.out = eg.intval << 8;
}
内部的には、16bit で値を計算して、上位8bit だけを出力としている。
ATTACK は、(計算が楽なので)線形で立ち上げることにした。DECAYは、減衰曲線で sustain_lvlになるまで減衰させる。SUSTAIN が終われば、RELEASEで減衰させるが、一応 DECAY と別のパラメータにした。
特別な処理としては、ATTACK のパラメータが 0 なら、SUSTAIN から入る。とか sustain_lvl が 0 なら ATTACK - RELEASE になるとか。ちなみに、CASE 文を使わないのは、(今使っている) avr-gcc(version 3.4) ではコード量が大きくなってしまうから。(CASE 文を使わないくせがついてしまった)
あと、fmul16()とかfmulsu16()とか出てくるが、実機では、アセンブラ化するつもりで関数化している。
おわりに
EG が付いたので、なんだか楽器らしくなってきて音を鳴らすのが楽しくなってきた。デジタルフィルタの VCF を作れば、一応コンポーネントは揃うことになる。もう一息なのだが、VCF は計算量が多く実機とすりあわせていかないといけないので、作るのはひとまずおいておいて ... 実機のコードと それにあわせた評価環境を作ろうと思う。
今のコードは、簡単なUIとサウンドジェネレータが一体になっているので、これを2つに分ける。Linux や Windows でもテストしたいので、プログラム間は、TCP/IP で通信する予定。
追記:
Linux/Windows の両方でバグがあったので、ddstest2.c を修正して、ddstest2a.c にアップデートしました。