pp.279-282
互いに位相のずれてゆく複数の波形(ここではのこぎり波を4波)を合成してフェイザーの効果を出してみる。
youtu.beyoutu.be
4波とも位相をずらさないで合成したときの波形:
#include <avr/io.h> #include <math.h> #define NUM_OF_OSCILLATORS 4 // 何波合成するか。2,4,8,16。 #define BASEPITCH 600 // 波形テーブルの要素数が2^16個だと假定して何個おきに飛ばすか。これで音程が決まる。 #define PHASE_RATE 18 // どんどんずらしてゆく位相差。 void createSaw(int8_t* tbl){ for(int i=0; i<256; i++){ tbl[i] = (int8_t)i; } } void zeros(uint16_t* tbl, int n){ for(int i=0; i<n; i++){ tbl[i] = 0; } } void initTimer0(){ TCCR0A |= (1 << WGM00) | (1 << WGM01); // ウェーブフォームモードの設定: Fast PWMモード TCCR0A |= (1 << COM0A1); // 出力モードの設定: コンペアマッチ時にOC0A (PD6)をクリア、ボトム時にOC0A (PD6)をセット TCCR0B |= (1 << CS00); // 分周比の設定: 1分周。8 MHzをそのままカウントする。 } int main(void) { uint8_t OSCILLATOR_SHIFT = (uint8_t)(log(NUM_OF_OSCILLATORS)/log(2)); // たとえば16波合成したら最終的に16で割る(右に4ビットシフトする)。 int8_t fullSaw[256]; createSaw(fullSaw); // のこぎり波を1周期分作る。 uint16_t accumulators[NUM_OF_OSCILLATORS]; // n波合成するときはアキュミュレーターをn個用意して、 zeros(accumulators, NUM_OF_OSCILLATORS); // 最初は全波とも位相を揃えておく。 uint8_t waveStep; // 256要素の波形テーブルの何番目の要素を読むか。 int16_t mixer ; // n波の合算値。 initTimer0(); DDRD |= (1 << PD6); // PD6 (OC0A)のIOをOUTにしてこの端子からDDS (pwm波)を出力することにする。 while(1){ mixer = 0; // n波の合算値を一旦ゼロに戻して、 for(int i=0; i<NUM_OF_OSCILLATORS; i++){ // n個あるアキュミュレーターごとに、 accumulators[i] += BASEPITCH; // 読み出す波形要素を(2^16)個の中から決定して、 waveStep = accumulators[i] >> 8; // それを(2^8)個にスケーリングして、 mixer += fullSaw[waveStep]; // 互いに位相のずれたn波を合算するが、 if(waveStep == 0){ // もし読み出す位置が0番目であったら、(※サウンドの1周期に約1回発生) accumulators[i] += PHASE_RATE * i; // n個あるアキュミュレーターの現在位置を次回のwhileループ用にそれぞれずらして、 } } mixer >>= OSCILLATOR_SHIFT; // n波を合算したのだから合算値をnで割って、 loop_until_bit_is_set(TIFR0, TOV0); // タイマー0の1周期がおわる(Timer/Counter0 Overflow Flagがセットされる)のを待ってから、 OCR0A = 128 + mixer; // コンペア値を更新して、(※128を足しているのは-128~127を0~255にするため) TIFR0 |= (1 << TOV0); // 再度Timer/Counter0 Overflow Flagを手動でクリアする。 } return 0; }