pp.282-287
その他: 音楽・動画・ゲームに活用! ソフトシンセ 音作り大全, p.22
いわゆるADSR方式でエンベロープカーブが設定できるようにしてみる。
lookupPitch()
で返しているC1
やC2
はscale.hに定義してある。
#include <avr/io.h> #include "scale.h" extern "C"{ #include "USART.h" } #define FULL_VOL 31 // 最大音量。5ビット値(0~31)。 #define ATTACK_RATE 50 // 小さいほど鋭く最大音量まで立ち上がる。 #define DECAY_RATE 300 // 小さいほど鋭くsustainレベルまで減衰する。 #define SUSTAIN_LEVEL 10 #define SUSTAIN_TIME 10000 #define RELEASE_RATE 500 //小さいほど鋭く音量ゼロまで減衰する。 #define ATTACK_TIME (ATTACK_RATE * FULL_VOL) #define DECAY_TIME (ATTACK_TIME + (FULL_VOL - SUSTAIN_LEVEL) * DECAY_RATE) #define RELEASE_TIME (DECAY_TIME + SUSTAIN_TIME) void createSaw(int8_t* tbl){ for(int i=0; i<256; i++){ tbl[i] = (int8_t)i; } } void initTimer0(){ TCCR0A |= (1 << WGM00) | (1 << WGM01); // Fast PWMモード TCCR0A |= (1 << COM0A1); // コンペアマッチ時にOC0A (PD6)をクリア、ボトム時にOC0A (PD6)をセット。 TCCR0B |= (1 << CS00); // 1分周。8 MHzをそのままカウントする。 } uint16_t lookupPitch(char ch){ switch(ch){ case 'a': return(G1); case 's': return(A1); case 'd': return(B1); case 'f': return(C2); case 'g': return(D2); case 'h': return(E2); case 'j': return(F2); case 'k': return(G2); case 'l': return(A2); case ';': return(B2); case ':': return(C3); default : return(C1); } } int main(void) { int8_t Saw[256]; createSaw(Saw); // のこぎり波を1周期分作る。 uint16_t accumulator = 0; uint8_t vol = 0; uint16_t noteClock = 0; uint16_t tuningWord = C1; uint8_t waveStep; int16_t mixer = 0; char serialInput; char isInAttack, isInDecay, isInRelease, AdsrComplete; initTimer0(); initUSART(); DDRD |= (1 << PD6); // PD6 (OC0A)にスピーカーをつないでそこからDDS (pwm)を出力することにする。 while(1){ accumulator += tuningWord; waveStep = accumulator >> 8; mixer = Saw[waveStep] * vol; mixer >>= 5; if(bit_is_set(UCSR0A, RXC0)){ serialInput = UDR0; printWord(serialInput); //transmitByte(serialInput); tuningWord = lookupPitch(serialInput); noteClock = 1; } if(noteClock){ (noteClock < ATTACK_TIME) && (noteClock > ATTACK_RATE * vol) && (vol < FULL_VOL)? isInAttack = 1: isInAttack = 0; (noteClock < DECAY_TIME) && ((noteClock - ATTACK_TIME) > (FULL_VOL - vol) * DECAY_RATE) && (vol > SUSTAIN_LEVEL)? isInDecay = 1: isInDecay = 0; (noteClock > RELEASE_TIME) && ((noteClock - RELEASE_TIME) > (SUSTAIN_LEVEL - vol) * RELEASE_RATE) && (vol > 0)? isInRelease = 1: isInRelease = 0; (noteClock > RELEASE_TIME) && ((noteClock - RELEASE_TIME) > (SUSTAIN_LEVEL - vol) * RELEASE_RATE) && (vol <= 0)? AdsrComplete = 1: AdsrComplete = 0; noteClock++; // ADSRの4フェーズが終了するまでインクリメントし続ける。 if (isInAttack) {vol++;} // attackフェーズが終わるまで音量を上げ続ける。 else if(isInDecay) {vol--;} // decayフェーズが終わるまで音量を下げ続ける。 else if(isInRelease) {vol--;} // releaseフェーズが終わるまで音量を下げ続ける。 else if(AdsrComplete){noteClock = 0;} // ADSRの4フェーズが終わったら、 } loop_until_bit_is_set(TIFR0, TOV0); // タイマーの1周期が終わるのを待ってから、 OCR0A = 128 + (uint8_t)mixer; // 次のパルス幅をセットして、 TIFR0 |= (1 << TOV0); // オーバーフローフラグを手動でクリアする。 } return 0; }
これでChapter 13: Advanced PWM Tricksが終わり。