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()