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(¬e, args->value, mml_opt.bticks); } break; case MML_TYPE_NOTE: { MML_ARGS_NOTE *args = &(p->args.note); note_tone(¬e, args->number, args->ticks); } break; case MML_TYPE_REST: { MML_ARGS_REST *args = &(p->args.rest); note_rest(¬e, 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(¬e, 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); } } }
参考
※コメント投稿者のブログIDはブログ作成者のみに通知されます