FPGA 版 FM 音源 (24) -- YMF262 測定 (16)
エンベロープ・ジェネレータ・シミュレータのスケッチ "EG.pde" を下に示します。
16 MHz クロックの ATmega328P を使用した Arduino 互換ボードと、Arduino 0022 IDE 環境でコンパイルして確認してあります。
スケッチのバイナリ・サイズは 4 KB 程度なので、ATmega168 でも問題なく動くと思います。
Arduino IDE の機能を使ってアーカイブしたスケッチは (→こちら) に置いてあります。
// 15-bit EG acc, lb(4.8) output EG (AR/DR) // for Arduino (F_CPU = 16 MHz, ATmega168 / ATmega328) // with no oversampling serial input stereo DAC // (ROHM BU9480F) // 2011/02/15 #include <avr/pgmspace.h> #include <avr/interrupt.h> #include <compat/ina90.h> #include <util/atomic.h> #define USE_LCD 1 #define USE_NOSDAC 1 //#define USE_WAVEQUE 1 //#define USE_SPI_STC 1 #define USE_SOUND 1 #define USE_ASM 1 //#define PRINT_FLAG 1 //#define LCD_PRINT 1 #define COM_BPS (115200) // disable DAC and SOUND if PRINT required #if (PRINT_FLAG | LCD_PRINT) #define STRETCH (0) #if defined(USE_SOUND) #undef USE_SOUND #endif #if defined(USE_NOSDAC) #undef USE_NOSDAC #endif #else #define STRETCH (i < 0x4000) #endif // // TIMER1 pin definition // #define OC1A_PIN (9) // // SPI pin definition // #define SS_PIN (10) #define MOSI_PIN (11) #define MISO_PIN (12) #define SCK_PIN (13) #define LRCK_PIN (3) // wave queue size (must be powers of 2) #define WAVEQUESIZE (128) // // DAC buffer variable // volatile int16_t dac_l, dac_r; volatile uint8_t fsflag; #if (USE_WAVEQUE) volatile uint8_t wq_wr_ix, wq_rd_ix; typedef struct wave_que_entry_t_tag { int16_t Lch; int16_t Rch; } wave_que_entry_t; wave_que_entry_t wave_que[WAVEQUESIZE]; #endif #if defined(USE_LCD) #include <LiquidCrystal.h> #define LCD_ROWS (2) #define LCD_COLS (16) #define LCD_RS (6) #define LCD_EN (7) #define LCD_D7 (2) #define LCD_D6 (16) #define LCD_D5 (15) #define LCD_D4 (14) #endif #if (USE_NOSDAC) // // DAC buffer to explode one int16_t into two uint8_t // static volatile union { int16_t w; uint8_t b[2]; } dac_save, dac_save2; // static volatile union dac_save{} // // Timer2 COMPare match A interrupt service routine // xmit upper byte to 16-bit DAC via 8-bit SPI // ISR(TIMER2_COMPA_vect) { SPSR; // read SPSR to clear SPIF if (_BV(PIND3) & PIND) { // get LRCK level #if (USE_WAVEQUE) dac_save.w = wave_que[wq_rd_ix].Lch; dac_save2.w = wave_que[wq_rd_ix].Rch; wq_rd_ix = (wq_rd_ix + 1) & (WAVEQUESIZE-1); #else dac_save.w = dac_l; // save DAC L ch data for later use fsflag = 1; // notify fs update timing to main loop #endif } else { #if (USE_WAVEQUE) // dac_save.w = wave_que[wq_rd_ix].Rch; // wq_rd_ix = (wq_rd_ix + 1) & (WAVEQUESIZE-1); dac_save.w = dac_save2.w; #else dac_save.w = dac_r; // save DAC R ch data for later use #endif }// if () SPDR = dac_save.b[1]; // xmit high byte #if (USE_SPI_STC) SPCR |= (_BV(SPIE)); // enable SPI interrupt to xmit low byte #else _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); SPDR = dac_save.b[0]; // xmit low byte #endif } // ISR(TIMER2_COMPA_vect) // // SPI Serial Transfer Complete interrupt service routine // xmit lower byte to 16-bit DAC via 8-bit SPI // ISR(SPI_STC_vect) { SPDR = dac_save.b[0]; // low byte SPCR &= (~_BV(SPIE)); // disable SPI interrupt } // ISR(SPI_STC_vect) // // SPI setup to support Non-oversampling DAC // void NosDAC_setup(int8_t DAC_mode) { // modify Timer2 setting for LRCK generateion analogWrite(LRCK_PIN,0); // enable LRCK output TCCR2B = _BV(CS20); // 1/1 clk (16 MHz) TCCR2A = 0x12; // CTC mode // OC2A disconn, OC2B toggle OCR2A = 181-1; // MAX = 180 (16 MHz / 181 / 2 = 44.199 kHz) TIMSK2 |= _BV(OCIE2A); // enable TIMER2 OCF2A interrupt // // TIMER1 setup for 442 Hz generation // pinMode(OC1A_PIN, OUTPUT); TCCR1A = _BV(COM1A0); // OC1A toggle at compare match TCCR1B = (_BV(WGM12) | _BV(CS10)); // CTC mode, 1/1 clk OCR1A = (181 * 100) - 1; // 16 MHz / (181 * 100) / 2 = 441.99 Hz // // SPI setup for serial 16 bit digital audio DAC // set SCK, MOSI to OUTPUT pinMode(SCK_PIN, OUTPUT); pinMode(MOSI_PIN, OUTPUT); // to assure MASTER operation, set SS to OUTPUT pinMode(SS_PIN, OUTPUT); // MISO forced to INPUT by MASTER mode, enable pullup pinMode(MISO_PIN, INPUT); digitalWrite(MISO_PIN, HIGH); // enable pullup at MISO // enable SPI, fosc/2, Master, CPHA=0, DORD=0, CPOL=0 SPCR = (_BV(SPE) | _BV(MSTR) ); SPSR = _BV(SPI2X); } // void NosDAC_setup() #endif // #if (USE_NOSDAC) typedef enum eg_states { EG_OFF, EG_AINIT, EG_ATTACK, EG_A2D, EG_DECAY, EG_SUSTAIN, EG_RELEASE } eg_stat_t; // prescaler carry bit mask #define PSC_CMASK (0x0200) // EG acc constant for 0dB, -96dB #define EG_0dB (0x0000) #define EG_96dB (0x7ff8) PROGMEM prog_uint16_t lb2lin_tab[256] = { 4084, 4074, 4062, 4052, 4040, 4030, 4020, 4008, 3998, 3986, 3976, 3966, 3954, 3944, 3932, 3922, 3912, 3902, 3890, 3880, 3870, 3860, 3848, 3838, 3828, 3818, 3808, 3796, 3786, 3776, 3766, 3756, 3746, 3736, 3726, 3716, 3706, 3696, 3686, 3676, 3666, 3656, 3646, 3636, 3626, 3616, 3606, 3596, 3588, 3578, 3568, 3558, 3548, 3538, 3530, 3520, 3510, 3500, 3492, 3482, 3472, 3464, 3454, 3444, 3434, 3426, 3416, 3408, 3398, 3388, 3380, 3370, 3362, 3352, 3344, 3334, 3326, 3316, 3308, 3298, 3290, 3280, 3272, 3262, 3254, 3246, 3236, 3228, 3218, 3210, 3202, 3192, 3184, 3176, 3168, 3158, 3150, 3142, 3132, 3124, 3116, 3108, 3100, 3090, 3082, 3074, 3066, 3058, 3050, 3040, 3032, 3024, 3016, 3008, 3000, 2992, 2984, 2976, 2968, 2960, 2952, 2944, 2936, 2928, 2920, 2912, 2904, 2896, 2888, 2880, 2872, 2866, 2858, 2850, 2842, 2834, 2826, 2818, 2812, 2804, 2796, 2788, 2782, 2774, 2766, 2758, 2752, 2744, 2736, 2728, 2722, 2714, 2706, 2700, 2692, 2684, 2678, 2670, 2664, 2656, 2648, 2642, 2634, 2628, 2620, 2614, 2606, 2600, 2592, 2584, 2578, 2572, 2564, 2558, 2550, 2544, 2536, 2530, 2522, 2516, 2510, 2502, 2496, 2488, 2482, 2476, 2468, 2462, 2456, 2448, 2442, 2436, 2428, 2422, 2416, 2410, 2402, 2396, 2390, 2384, 2376, 2370, 2364, 2358, 2352, 2344, 2338, 2332, 2326, 2320, 2314, 2308, 2300, 2294, 2288, 2282, 2276, 2270, 2264, 2258, 2252, 2246, 2240, 2234, 2228, 2222, 2216, 2210, 2204, 2198, 2192, 2186, 2180, 2174, 2168, 2162, 2156, 2150, 2144, 2138, 2132, 2128, 2122, 2116, 2110, 2104, 2098, 2092, 2088, 2082, 2076, 2070, 2064, 2060, 2054, 2048, }; int8_t rate, rof10, m_rate; uint8_t rof_mult, rof_mult2; int8_t hi_rate_flag; uint16_t eg_acc, psc_acc, psc_inc; int8_t ar, dr, rof; int8_t eg_stat; // // lb-to-lin conversion with sign // int lb2lin(int sign, int lb) { int lin, sft; sft = (lb >> 8 ); // integer part of lb value lb = (lb & 0xFF); // fraction part of lb value // force 0 if too small lin = ( (15 < sft) ? 0 : (pgm_read_word(&(lb2lin_tab[lb])) >> sft) ); return((-sign) ^ lin ); // 1's complementer } // int lb2lin() // EG rate setup void EG_set_rate(int rate_ini, int rof_ini) { rate = (rate_ini << 2) + rof_ini; // initial rate value rof10 = 0x03 & rate; // LS 2 bits rate >>= 2; // MS 4 bits if (0 == rate) { rof10 = 0;} if (15 <= rate) { // clip to 15 rate = 15; rof10 = 0; // no fractions } hi_rate_flag = (10 <= rate); psc_acc = 0; // reset prescaler if (hi_rate_flag) { // for hi-rate psc_inc = PSC_CMASK; // for continuous clock m_rate = rate; } else { // for lo-rate, set prescaler incr. psc_inc = (0x1 << ((0x0f & rate)-1)); m_rate = 10; // saturate to 10 } rof_mult = ((4 + rof10) << (m_rate-10)); rof_mult2 = rof_mult * 2; if (0 == rate) { rof_mult2 = 0; } // if (0 == rate) } // void EG_set_rate() // EG initialize void EG_init( void ) { EG_set_rate(0, 0); // rate = 0 for no acc update eg_acc = EG_96dB; // minimum level eg_stat = EG_OFF; // set to OFF state } // void EG_init() int EG_module( void ) { int clk_en; clk_en = hi_rate_flag; // continuous clock for hi-rate // clock prescaler and rate multiplier psc_acc += psc_inc; // increment prescaler acc // test for prescaler carry if (PSC_CMASK & psc_acc) { // prescaler carry? psc_acc &= (~PSC_CMASK); // clear prescaler carry clk_en = 1; } // if (PSC_CMASK ... // EG accumulator update if (clk_en | (EG_A2D == eg_stat) | (EG_AINIT == eg_stat)) { switch (eg_stat) { case EG_AINIT: // init for ATTACK EG_set_rate(ar, rof); // set attack rate and init psc eg_stat++; // advance to next state break; case EG_ATTACK: // if rate = 15, attack time is "0" if (15 == rate) { eg_acc = EG_0dB; } if (EG_0dB == eg_acc) { // is it 0 dB? eg_stat++; // advance to next state } else { if (0 < rate) { if (0x7f00 & eg_acc) { // far from peak // add 1's complement of Rof multiplied eg_acc #if (USE_ASM) __asm__ __volatile__ ( // code "mul %B0,%2 \n\t" "com r0 \n\t" "com r1 \n\t" "add %A0,r0 \n\t" "adc %B0,r1 \n\t" "clr r1 \n\t" // output register : "=&w"(eg_acc) // %0 // input register : "0"(eg_acc), // %1 "r"(rof_mult) // %2 // clobber : "r1" ); // __asm__ () #else eg_acc += ~(rof_mult * (eg_acc >> 8)); #endif } else { // near to peak eg_acc += ~rof_mult; // constant decrement if (0x8000 & eg_acc) { // overflow? eg_acc = EG_0dB; // saturate to 0dB eg_stat++; // advance to next state } // if (0x8000 & eg_acc) ... } // if ( } // if (0 < rate) } // if (0 == eg_acc) ... break; case EG_A2D: // ATTACK to DECAY transition EG_set_rate(dr, rof); // set decay rate and init psc eg_stat++; // advance to next state break; case EG_DECAY: case EG_RELEASE: // increment EG acc by shifted Rof eg_acc += rof_mult2; if (0x8000 & eg_acc) { // EG OFF? eg_acc = EG_96dB; eg_stat = EG_OFF; } // if (EG_96dB <= eg_acc) break; case EG_OFF: // no EG update break; default: break; } // switch (eg_stat) { ... } // if (clk_en) ... return(eg_acc); } // int EG_module() #if (USE_LCD) LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7); #endif void setup( void ) { #if (PRINT_FLAG) Serial.begin(COM_BPS); #endif #ifdef USE_LCD lcd.begin(LCD_COLS, LCD_ROWS); #endif #if (USE_WAVEQUE) wq_wr_ix = 0; // wave queue write index = 0 wq_rd_ix = 0; // wave queue read index = 0 #endif #if (USE_NOSDAC) NosDAC_setup(0); #endif #if (USE_LCD) lcd.setCursor(0, 0); lcd.print("AR= "); #if (LCD_PRINT) lcd.print("Lin= "); #endif lcd.setCursor(0, 1); lcd.print("DR= : "); #if (LCD_PRINT) lcd.print("Acc= "); #endif #endif } // void setup() void loop( void ) { int32_t i; int16_t eg_out, op_out; int8_t sign; #if (USE_SOUND) int8_t fcnt = 0; #endif for (ar = 14; ar >= 8; ar--) { dr = ar - 3; #if (USE_LCD) lcd.setCursor(3, 0); if (10 > (ar)) { lcd.print(' '); } // if lcd.print(ar, DEC); lcd.setCursor(3, 1); if (10 > (dr)) { lcd.print(' '); } // if lcd.print(dr, DEC); #endif for (rof = 3; rof >= 0; rof--) { #if (USE_LCD) lcd.setCursor(6, 1); lcd.print(rof, DEC); // lcd.print(" "); #endif #if (PRINT_FLAG) Serial.print("# ar="); Serial.print(ar, DEC); Serial.print(", rof="); Serial.print(rof, DEC); Serial.println(); #endif sign = 0; EG_init(); // initialize EG vars eg_stat = EG_AINIT; for (i = 0; ((EG_OFF != eg_stat) | STRETCH) ; i++) { eg_out = EG_module(); // compute EG // convert lb to linear value op_out = lb2lin(sign, (eg_out >> 3)); #if (USE_SOUND) if (50 <= ++fcnt) { sign = !sign; fcnt = 0; } // if #endif #if (USE_NOSDAC) #if (USE_WAVEQUE) // check wave queue has room to write while (wq_wr_ix == wq_rd_ix) { } wave_que[wq_wr_ix].Lch = (op_out << 3); wave_que[wq_wr_ix].Rch = (eg_out); wq_wr_ix = (wq_wr_ix + 1) & (WAVEQUESIZE-1); #else while (!fsflag) {}; ATOMIC_BLOCK(ATOMIC_FORCEON) { dac_l = (op_out << 3); dac_r = (eg_out); fsflag = 0; } // ATOMIC_BLOCK() #endif // USE_WAVEQUE #endif // USE_NOSDAC #if (USE_LCD & LCD_PRINT) lcd.setCursor(12, 0); if (1000 > op_out) lcd.print(' '); if (100 > op_out) lcd.print(' '); if (10 > op_out) lcd.print(' '); lcd.print(op_out, DEC); lcd.setCursor(12, 1); if (0x1000 > eg_out) lcd.print(' '); if (0x100 > eg_out) lcd.print(' '); if (0x10 > eg_out) lcd.print(' '); lcd.print(eg_out, HEX); #endif #if (PRINT_FLAG) Serial.print(i, DEC); Serial.print(" "); Serial.print(op_out, DEC); Serial.print(" "); Serial.print(eg_out, DEC); Serial.println(); #endif } // for (i = 0; ... // 2 empty lines for GNUPLOT data block separator #if (PRINT_FLAG) Serial.println(); Serial.println(); #endif } // for (rof = 0; ... } // for (ar = 13; ... } // void loop()