DDS (direct-digital synthesis) / フェイザーを通したような音を出す

pp.279-282

互いに位相のずれてゆく複数の波形(ここではのこぎり波を4波)を合成してフェイザーの効果を出してみる。
youtu.beyoutu.be

4波とも位相をずらさないで合成したときの波形:
f:id:ti-nspire:20191101090908p:plain:w500

#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;
}

f:id:ti-nspire:20191101091505p:plain:w500