goo blog サービス終了のお知らせ 

忘備録-備忘録

技術的な備忘録

ch32v003で電子オルゴール その2

2025-02-07 20:40:42 | ch32v003
A tiny MML parserを使用した電子オルゴールその2です。
発音にDDS方式で正弦波を発生させ、エンベローブのデータを掛け合わせ、少しでもオルゴールの音質を再現できるようにしました。DA変換にはPWM方式を用いています。出力値を矩形波のON-OFFから中間値(256段階)をとることができるので、和音の出力も可能です。
//                       CH32V003J4M6
//                          +------+
//  signal <-- TIM1_CH2/PA1-+1    8+-PD1/SWIO
//                      GND-+2    7+-PC4/TIM1_CH4 --> signal
//                      PA2-+3    6+-PC2
//                      VDD-+4    5+-PC1
//                          +------+
// 電子オルゴール
// A tiny MML parser(https://cubeatsystems.com/tinymml/)を使用
// song.hはA tiny MML parserの/src/ample/arduino_chord_exampleにあるもの
// Direct Digital Synthesizer (DDS)方式で発音する
// 和音再生可能
// サンプリング周波数 31250Hz

#include "ch32v003fun.h"
#include <stdio.h>
#include "mml.h"
#include "song.h"

// エンベローブデータ
const int8_t envData[] = 
{
    127,  60,  80,  90, 100, 110, 127, 127, 127, 127, 127, 110, 100,  90,  80,  75,
     73,  71,  69,  67,  65,  63,  61,  59,  57,  55,  54,  52,  50,  48,  47,  45,
     43,  42,  40,  39,  37,  36,  34,  33,  31,  30,  29,  27,  26,  25,  24,  22,
     21,  20,  19,  18,  17,  16,  15,  14,  13,  12,  11,  10,  10,   9,   8,   7,
      7,   6,   6,   5,   4,   4,   3,   3,   2,   2,   2,   1,   1,   0
};
// sin波テーブル
const int8_t sinData[256] = 
{
    0,     3,    6,    9,   12,   15,   18,   21,   24,   28,   31,   34,   37,   40,   43,   46,
   48,    51,   54,   57,   60,   63,   65,   68,   71,   73,   76,   78,   81,   83,   85,   88,
   90,    92,   94,   96,   98,  100,  102,  104,  106,  107,  109,  111,  112,  114,  115,  116,
   118,  119,  120,  121,  122,  123,  123,  124,  125,  125,  126,  126,  126,  127,  127,  127,
   127,  127,  127,  127,  126,  126,  125,  125,  124,  124,  123,  122,  121,  120,  119,  118,
   117,  116,  114,  113,  111,  110,  108,  107,  105,  103,  101,   99,   97,   95,   93,   91,
    89,   87,   84,   82,   79,   77,   74,   72,   69,   67,   64,   61,   58,   56,   53,   50,
    47,   44,   41,   38,   35,   32,   29,   26,   23,   20,   17,   14,   10,    7,    4,    1,
    -2,   -5,   -8,  -11,  -15,  -18,  -21,  -24,  -27,  -30,  -33,  -36,  -39,  -42,  -45,  -48,
   -51,  -54,  -57,  -59,  -62,  -65,  -68,  -70,  -73,  -75,  -78,  -80,  -83,  -85,  -88,  -90,
   -92,  -94,  -96,  -98, -100, -102, -104, -106, -108, -109, -111, -112, -114, -115, -117, -118,
  -119, -120, -121, -122, -123, -124, -125, -125, -126, -126, -127, -127, -128, -128, -128, -128,
  -128, -128, -128, -127, -127, -127, -126, -126, -125, -124, -124, -123, -122, -121, -120, -119,
  -117, -116, -115, -113, -112, -110, -108, -107, -105, -103, -101,  -99,  -97,  -95,  -93,  -91,
   -89,  -86,  -84,  -82,  -79,  -77,  -74,  -72,  -69,  -66,  -64,  -61,  -58,  -55,  -52,  -49,
   -47,  -44,  -41,  -38,  -35,  -32,  -29,  -25,  -22,  -19,  -16,  -13,  -10,   -7,   -4,    0,
};
// 音階データ
const uint16_t notefreq[] = 
{
       67,   71,   75,   79,   84,   89,   94,  100,  106,  112,  119,  126,
      133,  141,  150,  159,  168,  178,  189,  200,  212,  224,  238,  252,
      267,  283,  299,  317,  336,  356,  377,  400,  423,  448,  475,  503,
      533,  565,  599,  634,  672,  712,  754,  799,  847,  897,  950, 1007,
     1067, 1130, 1197, 1268, 1344, 1424, 1508, 1598, 1693, 1794, 1900, 2013,
     2133, 2260, 2394, 2537, 2688, 2848, 3017, 3196, 3386, 3588, 3801, 4027,
     4266, 4520, 4789, 5074, 5375, 5695, 6034, 6392, 6773, 7175, 7602, 8054,
     8533, 9040, 9578,10147,10751,11390,12067,12785,13545,14351,15204,16108,
    17066,18081,19156,20295,21502,22780,24135,25570,27090,28701,30408,32216,
};

typedef struct
{
    uint32_t bpm;
    uint32_t bticks;
} NOTE;

// TIM1をコPWMモードで初期化
// Output pin CH4(PC4)
#define TIM1_DEFAULT 0x00
void t1pwm_init(void)
{
    // クロック供給 GPIOC and TIM1
    RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1;
    // PC4 is T1CH4, 10MHz Output alt func, push-pull
    GPIOC->CFGLR &= ~(0xf << (4 * 4));
    GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 4);
    GPIOA->CFGLR &= ~(0xf << (4 * 1));
    GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * 1); // TIM1のリセット
    RCC->APB2PRSTR |= RCC_APB2Periph_TIM1;
    RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;

    // SMCFGR: 入力の初期値 CK_INT
    // プリスケーラ設定 分周比は設定値+1
    TIM1->PSC = 5;
    // PWM幅の設定 8bit
    TIM1->ATRLR = 255;
    // 48MHz/(5+1)(PSC)/256(ATRLR) = 31250Hz PWM周波数

    // TIM_OC4M_2 | TIM_OC4M_1 PWM1モード設定
    // TIM_OC4PE オートリロード設定
    TIM1->CHCTLR1 |= TIM_OC2M_2 | TIM_OC2M_1 | TIM_OC2PE;
    TIM1->CHCTLR2 |= TIM_OC4M_2 | TIM_OC4M_1 | TIM_OC4PE;
    // ATRLRレジスタ オートリロード
    TIM1->CTLR1 |= TIM_ARPE;

    // Enable Channel outputs, set default state (based on TIM1_DEFAULT)
    TIM1->CCER |= TIM_CC4E | (TIM_CC4P & TIM1_DEFAULT);
    TIM1->CCER |= TIM_CC2E | (TIM_CC2P & ~TIM1_DEFAULT);
    // アップデートで割り込み発生
    TIM1->DMAINTENR |= TIM_UIE;
    // TIM1割り込み許可
    NVIC_EnableIRQ(TIM1_UP_IRQn);
    // 設定反映
    TIM1->SWEVGR |= TIM_UG;
    // TIM1 出力許可
    TIM1->BDTR |= TIM_MOE;
    // 出力変化しない
    TIM1->CH4CVR = TIM1->ATRLR + 1;
    TIM1->CH2CVR = TIM1->ATRLR + 1;
    // TIM2スタート
    TIM1->CTLR1 |= TIM_CEN;
}

// 最大同時発音数
#define MAX_NOTES 4
// 周波数の増加分
volatile uint16_t FrqDiff[MAX_NOTES];
// 発音時間 エンベローブ計算用
volatile uint16_t otime[MAX_NOTES];

// タイマ割り込み
void TIM1_UP_IRQHandler(void) __attribute__((interrupt));
void TIM1_UP_IRQHandler(void)
{
    // Clear the trigger state for the next IRQ
    TIM1->INTFR = 0x00000000;

    uint8_t phase, envc;
    static uint16_t sinCount[MAX_NOTES];
    int datasum = 0;

    for(int i=0;i<MAX_NOTES;i++) {
        sinCount[i] = sinCount[i] + FrqDiff[i];
        if (FrqDiff[i]==0)
            sinCount[i] = 0;
        phase = sinCount[i] >> 8; // 位相はsinCountの上位8bit
                                          // phaseは256あるのでオーバーフローしない
        envc = otime[i] >> 8;
        if (envc < 78)
        {
            datasum += ((int)envData[envc]) * ((int)sinData[phase]);
        }
        otime[i]++;
    }
    datasum = datasum >> 6; //ボリューム調整
    if (datasum > 127)
    {
        datasum = 127;
    }
    else if (datasum < -128)
    {
        datasum = -128;
    }
    TIM1->CH4CVR = TIM1->CH2CVR = 128 + datasum;
}

// 設定した周波数の矩形波(デューティ50%)を出力
//   frq : 周波数
//   tim : 出力する時間[msec]
void tone(uint32_t freq, uint32_t tim)
{
    static uint16_t freqlist[MAX_NOTES];
    static int count = 0;
    if (freq == 0)
    {
        // 出力停止 無音
        for (int i = 0; i < MAX_NOTES; i++)
        {
            FrqDiff[i] = 0;
            otime[i] = 0;
        }
        Delay_Ms(tim);
        count = 0;
        return;
    }
    if (tim == 0)
    {
        if (count < MAX_NOTES)
        {
            freqlist[count] = freq;
            count++;
        }
    }
    else
    {
        // 出力周波数設定
        if (count == 0) // 単音
        {
            for (int i = 0; i < MAX_NOTES; i++)
            {
                FrqDiff[i] = 0;
                otime[i] = 0;
            }
            FrqDiff[0] = freq;
        }
        else            // 和音
        {
            for (int i = 0; i < MAX_NOTES; i++)
            {
                FrqDiff[i] = freqlist[i];
                otime[i] = 0;
            }
        }
    }
    Delay_Ms(tim);
    // 出力停止
    for (int i = 0; i < MAX_NOTES; i++)
    {
        FrqDiff[i] = 0;
        freqlist[i] = 0;
    }
    count = 0;
}

static uint32_t get_note_length_ms(NOTE *p, uint32_t note_ticks)
{
    return (60000) * note_ticks / p->bpm / p->bticks;
}

void note_init(NOTE *p, int bpm, int bticks)
{
    p->bpm = bpm;
    p->bticks = bticks;
}

void note_tone(NOTE *p, int number, int ticks)
{
    uint16_t freq = notefreq[number];
    uint32_t ms = get_note_length_ms(p, ticks);
    tone(freq, ms);
}

void note_rest(NOTE *p, int ticks)
{
    uint32_t ms = get_note_length_ms(p, ticks);
    tone(0, ms);
}

NOTE note;
MML mml;
MML_OPTION mml_opt;

static void mml_callback(MML_INFO *p, void *extobj)
{
    switch (p->type)
    {
    case MML_TYPE_TEMPO:
    {
        MML_ARGS_TEMPO *args = &(p->args.tempo);
        note_init(&note, args->value, mml_opt.bticks);
    }
    break;
    case MML_TYPE_NOTE:
    {
        MML_ARGS_NOTE *args = &(p->args.note);
        note_tone(&note, args->number, args->ticks);
    }
    break;
    case MML_TYPE_REST:
    {
        MML_ARGS_REST *args = &(p->args.rest);
        note_rest(&note, args->ticks);
    }
    break;
    default:
        break;
    }
}

int main(void)
{
    SystemInit();
    Delay_Ms(100);
    t1pwm_init();

    // Initialize the MML module.
    mml_init(&mml, mml_callback, 0);
    MML_OPTION_INITIALIZER_DEFAULT(&mml_opt);
    // Initialize the note module.
    int tempo_default = 120;
    note_init(&note, tempo_default, mml_opt.bticks);

    while (1)
    {
        const char *song_table[] = {
            song_over_world,
            song_level_clear,
        };
        int i;

        for (i = 0; i < sizeof(song_table) / sizeof(song_table[0]); i++)
        {
            // Setup the MML module.
            mml_setup(&mml, &mml_opt, (char *)song_table[i]);
            // Fetch the MML text command.
            while (mml_fetch(&mml) == MML_RESULT_OK)
            {
            }
            Delay_Ms(1000);
        }
    }
}

参考

最新の画像もっと見る

コメントを投稿

サービス終了に伴い、10月1日にコメント投稿機能を終了させていただく予定です。