既出のPWM オルゴールを改造して4和音のオルゴールにします。 複雑な波形が出力できます。ただし、圧電スピーカーでは、良い音になりません。写真の様な外部スピーカアンプに簡単なアッテネーターとローパスフィルターを取り付けると深みのある音が聞こえてきます。取り扱える音域も6オクターブに拡大してあります。
PWM周期( 93.75kHz)毎に、異なったパートの演奏を行い、結果として4パートの楽譜演奏を行います。各パートの楽譜は、別々の配列に記録します。
掲載した楽譜ファイル gakufu.h には、3曲のデータが入っています。それぞれの曲の終わりは、 0xFFFF で示し、3曲目のデータには、 0xFFFF を2つ続けてファイルの終了とにします。
また、4和音になると楽譜をPICが理解するデーター(gakufu.h) に、コーディングするのが大変になったので、Visual C# 2010 で、PC画面上で楽譜を作成し、PIC用データに変換するPCのWindowsアプリケーションを作成しました。プログラムを次ページに紹介します。
プロジェクトは、曲のデータを持つ「gakufu.h」 と、曲を演奏するための「main.c」 から構成されています。
プロジェクト B06_orgel_waon.zip |
*注意*
MPLAB X プロジェクト には、PICkit3で書き込む仕様とブートローダで書き込む2通りの仕が設定されています。どちらを利用するかをメインメニューバーのIDE Configuration pull down で選んでください。
MPLAB IPEアプリを使用してPICkit3などで、直接PICにプログラムを書き込む場合には、以下から HEX ファイルをダウンロードして使用してください。
PICkit3_B06_Orgel_waon.zip |
main.c
/* File: main.c 1
* gakufu は、16 bit 単位
* パート1は、最初にテンポ(1分間に演奏する四分音符の数)が記録さる。
* その他のパートは そのパートの音量(0−127)が記録さる。
* パート1の音量は常に最大(127)
* それ以降は音符 MSB 8 bitが音の高さ。 半音単位で、中央のドが 60(0x3C)
* LSB 7 bitが音の長さ
* 二分音符:10、 四分音符:08、 八分音符:04、 十六分音符:02
* 付点二分音符:18、付点四分音符:0C、付点八分音符:06、付点十六分音符:03
* | 7 6 5 4 3 2 1 0| 7 6 5 4 3 2 1 0 |
* | 高さ | | 長さ |
*
* Language: MPLABX XC8 Target: PIC116F1459
* 20210410 rev1 : xc8 ver 2.20 に対応した
*/
// 音が減衰する程度 指定値(ms)で1/16減少
#define EnvelopeConst 50
// 音符と音符の間の無音時間(ms)
#define SilentTime 30
#include "xc.h"
#include "gakufu.h"
#define _XTAL_FREQ 48000000
#define SW2 RA5
#define p1 0
#define p2 1
#define p3 2
#define p4 3
#define waitStart 0
#define getGakufu 1
#define getOnpu 2
#define onpuStart 3
#define onpuPlay 4
#define onpuLast 5
#define onpuEnd 6
#define gkfEnd 7
#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config BOREN = ON, CLKOUTEN = OFF, IESO = OFF, FCMEN = OFF
#pragma config WRT = OFF, CPUDIV = NOCLKDIV, USBLSCLK = 48MHz, PLLMULT = 3x
#pragma config PLLEN = ENABLED, STVREN = ON, BORV = LO, LPBOR = OFF, LVP = OFF
const unsigned int dW_tbl[] = { // for 48MHz PWM Freq 93.75kHzx4
// Do, Do#, Re, Re#, Mi, Fa, Fa#, So, So#, Ra, Ra#, Si,
0,
0x0B7,0x0C2,0x0CD,0x0D9,0x0E6,0x0F4,0x103,0x112,0x122,0x134,0x146,0x159, //12
0x16E,0x184,0x19B,0x1B3,0x1CD,0x1E8,0x205,0x224,0x245,0x267,0x28C,0x2B2, //24
0x2DC,0x307,0x335,0x366,0x39A,0x3D0,0x40B,0x448,0x489,0x4CE,0x517,0x565, //36
0x5B7,0x60E,0x66A,0x6CC,0x733,0x7A1,0x815,0x890,0x913,0x99D,0xA2F,0xACA, //48
0xB6E, 0xC1C, 0xCD5, 0xD98, 0xE67, 0xF42, 0x102A,0x1120,0x1225,0x1339,0x145E,0x1594, //60
0x16DC,0x1838,0x19A9,0x1B30,0x1CCE,0x1E84,0x2055,0x2241,0x244A,0x2673,0x28BC,0x2B28, //72
}; // 0x5B7(index 37) が中心のド(60 0x3E) ,0x99D はラ (440Hz)
unsigned int accWave[] = {0,0,0,0};
unsigned int incWave[] = {0,0,0,0};
unsigned char amp[] = {0,0,0,0};
unsigned char wave[] = {0,0,0,0};
char SWdown=0; // SW 押下フラグ
char TmpCnt; // テンポ時間カウント
char SltCnt; // 消音時間カウント
unsigned char tempo; // 16分音符の時間 ms
char TmpFlg[] = {0,0,0,0}; // テンポ フラグ
char SltFlg[] = {0,0,0,0}; // 消音 フラグ
char EnvCnt[] = {1,1,1,1}; // 振幅調整時間カウント
// プロトタイプ
void SWcheck(void);
void EnvelopeCnt(char);
void TempoCnt(void);
void SilentCnt(void);
void Control(char);
unsigned int ReadOnp(char,unsigned int);
void main(void) {
OSCCON = 0b11111100; // SPLLEN = ON, 16MHz(x3=48MHz)
TRISC = 0b10110000; // PWM2,LEDs 出力設定
LATC = 0; // LEDs 消灯
nWPUEN = 0; // 弱プルアップ 有効
// Timer1 の設定 -------------------------
T1CON = 0b00000001; // Fosc/4 ps 1/1 TMR1ON
TMR1 = 0xD100; // +12032 (1ms) で Carry
TMR1IF = 0; // Timer1フラグクリア
// PWM の設定 -------------------------
PWM2DCL = 0;
PR2 = 0x7F; // Freq_PWM = 93.75kHz
T2CON = 0b00000000; // Fosc/4 ps 1/1 T2OFF
TMR2IF = 0; // Timer2フラグクリア
TMR2IE = 1; // Timer2割込み有効
PEIE = 1; // ペリフェラル割込み有効
GIE = 1; // 割込み有効
while(1){
if(TMR1IF){ // 1ms で Carry
TMR1H = 0xD1; // 次の 1ms を設定
TMR1IF = 0;
SWcheck(); // SW ONの確認
EnvelopeCnt(p1); // 振幅調整 50ms
EnvelopeCnt(p2); // 振幅調整 50ms
TempoCnt(); // テンポ調整
SilentCnt(); // 無音時間
}
Control(p1); // Part1 出力制御
Control(p2); // Part2 出力制御
Control(p3); // Part1 出力制御
Control(p4); // Part2 出力制御
} // while
}
// 割込み処置 --------------------------------------------
void __interrupt() isr(void){ // xc8 ver 2.x 用
static char ch;
if(TMR2IF){ // Timer2 割込みを確認
TMR2IF = 0; // Timer2 フラグ リセット
ch++; // 出力するPart番号を進める
ch &= 3; // 番号は0−3まで
accWave[ch] += incWave[ch]; // part繰返し波形更新
if(STATUSbits.CARRY)wave[ch] ^= 1; // キャリが出れば反転
if(wave[ch])PWM2DCH = amp[ch]; // 波形 Hi ならamp
else PWM2DCH = 0; // 波形 Low ならゼロを
} // 次サイクルで出力
}
//------- SWチャタリング防止 1msループ内 ----------------
void SWcheck(void){
static char SWnotRdy = 10; // SWupの確認待ち初期化
if(SWnotRdy){ // SWupの確認中なら
if(SW2)SWnotRdy--; // SWupの確認をー1
else SWnotRdy = 10; // SWupの確認初期値
}else if(!SW2){ // SWupの確認済みでDownなら
SWdown = 1; // SWが押されたマーク
SWnotRdy = 10; // SWupの確認待ちに
}
}
//------- 振幅調整 50ms -----------------------------------
void EnvelopeCnt(char pt){
if(EnvCnt[pt]-- == 1){
EnvCnt[pt] = EnvelopeConst; // 50ms毎に
amp[pt] = (int)amp[pt] * 15/16; // 音量を -6% する
}
}
//------- テンポ調整 -----------------------------------
void TempoCnt(void){
if(TmpCnt-- == 1){
TmpCnt = tempo; // テンポ毎に
TmpFlg[p1] = 1; // テンポフラグをセット
TmpFlg[p2] = 1;
TmpFlg[p3] = 1;
TmpFlg[p4] = 1;
}
}
//------- 無音時間 -----------------------------------
void SilentCnt(void){
if(SltCnt-- == 1){ // 消音時間になれば
SltFlg[p1] = 1; // 消音フラグをセット
SltFlg[p2] = 1;
SltFlg[p3] = 1;
SltFlg[p4] = 1;
}
}
//------- 主制御 -----------------------------------
void Control(char pt){
static char state[] = {0,0,0,0};
static int pOnp[] = {0,0,0,0}; // 音符位置
static unsigned char iAmp[4]; // パートの音量
static unsigned char tPlay[4]; // 演奏長さ
static unsigned int incNext[4]; // 次音符周期
static unsigned char ampNext[4]; // 次音符音量
unsigned int Onp;
char i;
//---------------------------------------------------------
switch(state[pt]){
case waitStart: // ====== 演奏開始待ち ======
if(SWdown){ // SW 押されるのを待つ
if(pt == p1){ // Part1だけ処理
SWdown=0; // SW 処理済み明示
state[p1] = getGakufu; // 演奏開始
}
}
break;
case getGakufu: // ====== 楽譜の設定 ======
if(p1gkf[pOnp[p1]]==0xFFFF){ // 楽譜終端なら
for(i=0;i<=3;i++){ // 全パートの楽譜を
pOnp[i] = 0; // 先頭にリセット
}
}
tempo = 7500/p1gkf[pOnp[p1]]; // 楽譜のテンポ取得
TmpCnt = tempo; // テンポセット
PWM2CON = 0b11000000; // PWM ON
TMR2ON = 1; // Timer2 ON
for(i=0;i<=3;i++){ // 全パートの
iAmp[i] = ReadOnp(i, pOnp[i]++); // 音量取得
TmpFlg[i] = 0; // テンポフラグりセット
state[i] = getOnpu; // 音符設定へ
}
iAmp[p1] = 0x7F; // Part1 音量は常に最大
break;
case getOnpu: // ====== 音符の設定 ======
Onp = ReadOnp(pt, pOnp[pt]++); // 該当楽譜から音符取得
if(Onp == 0xFFFF){ // Partの終りなら
state[pt] = gkfEnd; // 曲終了処理へ
break;
}
tPlay[pt] = Onp & 0x7F; // 演奏時間に変換
Onp = Onp >> 8; // 音の高さを取り出す
if((Onp>23)&(Onp<96)){ // 音符なら
incNext[pt] = dW_tbl[Onp - 23]; // 音高を繰返し時間に変換
ampNext[pt] = iAmp[pt]; // 最大音量
}else{ // 休符なら
incNext[pt] = 0; // 繰返し なし
ampNext[pt] = 0; // 音量OFF
}
state[pt] = onpuStart; // 音符演奏開始へ
break;
case onpuStart: // ====== 音符の演奏開始 ======
if(TmpFlg[pt]){ // テンポを待って
TmpFlg[pt] = 0; // 音符の演奏情報を更新
incWave[pt] = incNext[pt]; // 繰返し単位セット
accWave[pt] = 0; // 波形繰返しリセット
wave[pt] = 0; // 波形 OFF
EnvCnt[pt] = EnvelopeConst; // 音量減衰タイマリセット
amp[pt] = ampNext[pt]; // 音量セット
if(amp[pt])LATC |= 1 << pt; // 音符ならLED点灯
state[pt] = onpuPlay; // 音符演奏継続へ
}
break;
case onpuPlay: // ====== 音符の演奏継続 ======
if(TmpFlg[pt]){ // テンポを待って
TmpFlg[pt] = 0; // テンポフラグリセット
if(tPlay[pt]-- == 2) // 音符の演奏終了なら
state[pt] = onpuLast; // 音符の無音設定へ
if(SWdown){ // SWが押されれば
state[p1] = gkfEnd; // 曲の終了処理へ
SWdown=0; // SW 処理済み明示
}
}
break;
case onpuLast: // ====== 音符の無音時間設定 ======
LATC &= ‾(1 << pt); // LED OFF
SltCnt = tempo - SilentTime; // 無音時間をセット
SltFlg[pt] = 0; // 無音フラグをリセット
state[pt] = onpuEnd; // 音符の時間終了へ
break;
case onpuEnd: // ====== 音符の演奏終了 ======
if(SltFlg[pt]){ // 演奏終時間を待つ
amp[pt] = 0; // 音量OFF
state[pt] = getOnpu; // 次の音符取得へ
}
break;
case gkfEnd: // ====== 楽譜の演奏終了 ======
if(pt == p1){ // Part 1 終了なら
LATC &= 0xF0; // LED消灯
PWM2CON = 0; // PWM OFF
TMR2ON = 0; // Timer2 OFF
for(i=0;i<=3;i++){ // 全Partともに
pOnp[i]--; // 曲終了まで進め
while(ReadOnp(i, pOnp[i]++) != 0xFFFF);
state[i] = waitStart; // 演奏開始待ちに
}
}else{ // Part 1 以外なら
amp[pt] = 0; // 該当音量OFF
incWave[pt] = 0; // 繰返し単位 0
state[pt] = waitStart; // 次曲演奏待ちへ
}
break;
}
}
// 指定 part の楽譜から指定 index の音符取得
unsigned int ReadOnp(char part, unsigned int index){
switch(part){
case p1:return p1gkf[index];
case p2:return p2gkf[index];
case p3:return p3gkf[index];
case p4:return p4gkf[index];
}
return 0;
}
gakufu.h
const unsigned int p1gkf[] = {
84, // 夕焼け小焼け
0x4304,0x4304,0x4304,0x4504,0x4304,0x4304,0x4304,0x4004,
0x3C04,0x3C04,0x3E04,0x4004,0x3E0C,0x0004,0x4008,0x4004,
0x4304,0x4504,0x4804,0x4804,0x4504,0x4304,0x4304,0x4504,
0x4304,0x480C,0x0004,0x4806,0x4A02,0x4804,0x4504,0x4804,
0x4804,0x4304,0x4304,0x4504,0x4304,0x4504,0x4304,0x400C,
0x0004,0x4304,0x4004,0x3E04,0x3C04,0x3E04,0x3E04,0x3C04,
0x3E04,0x4004,0x4304,0x4504,0x4304,0x480C,0x0004,
0xFFFF,
92, // フランスの古い歌
0x3E04,0x4304,0x4504,0x4604,0x4804,0x4A0C,0x4A04,0x4804,
0x4A04,0x4B04,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,
0x4804,0x4A04,0x4B02,0x4A02,0x4804,0x4604,0x450E,0x4302,
0x430C,0x3E04,0x4304,0x4504,0x4604,0x4804,0x4A0C,0x4A04,
0x4804,0x4A04,0x4B04,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,
0x4B04,0x4804,0x4A04,0x4B02,0x4A02,0x4804,0x4604,0x450E,
0x4302,0x4310,0x4308,0x4304,0x4504,0x460C,0x4604,0x4808,
0x4808,0x450C,0x4504,0x4A0C,0x4A04,0x4B04,0x4D02,0x4B02,
0x4A04,0x4804,0x4608,0x4504,0x4304,0x450C,0x3E04,0x4304,
0x4504,0x4604,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,
0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,0x4804,0x4A04,
0x4B02,0x4A02,0x4804,0x4604,0x450E,0x4302,0x4310,
0xFFFF,
92, // かたつむり
0x4506,0x4502,0x4504,0x4204,0x3E06,0x3E02,0x3E04,0x4004,
0x4206,0x4202,0x4004,0x3E04,0x4008,0x0008,0x4206,0x4302,
0x4504,0x4704,0x4506,0x4502,0x4504,0x4204,0x4006,0x4002,
0x3E04,0x4004,0x4208,0x0008,0x4504,0x4A04,0x4A04,0x4504,
0x4204,0x4504,0x4504,0x4204,0x3E04,0x4204,0x4206,0x4002,
0x3E08,0x0008,
0xFFFF,
0xFFFF,
};
const unsigned int p2gkf[] = {
100, // 夕焼け小焼け
0x4008,0x4004,0x4104,0x4008,0x4304,0x4004,0x3C08,0x3B04,
0x3E04,0x3B0C,0x0004,0x3C08,0x3C04,0x4304,0x3C08,0x3C08,
0x4008,0x4108,0x400C,0x0004,0x4504,0x3C04,0x4504,0x3C04,
0x4304,0x3C04,0x4004,0x3C04,0x4104,0x4004,0x4104,0x4004,
0x400C,0x0004,0x4304,0x4004,0x3E04,0x3C04,0x3E08,0x3C04,
0x3E04,0x4004,0x4304,0x4504,0x4304,0x400C,0x0004,
0xFFFF,
100, // フランスの古い歌
0x0004,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,
0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,
0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,0x3F10,0x0010,
0x410C,0x0004,0x0010,0x0010,0x0010,0x0010,0x0010,0x0010,
0x3F08,0x3E08,0x3A10,
0xFFFF,
100, // かたつむり
0x4206,0x4202,0x4204,0x3E04,0x3E06,0x3E02,0x3E04,0x3D04,
0x3E06,0x3E02,0x3D04,0x3E04,0x3D08,0x0008,0x3E06,0x4002,
0x4204,0x4304,0x4206,0x4202,0x4204,0x3E04,0x3D06,0x3D02,
0x3E04,0x3D04,0x3E08,0x0008,0x4204,0x4204,0x4204,0x4204,
0x3E04,0x3E04,0x3E04,0x3E04,0x3E04,0x3E04,0x3E06,0x3D02,
0x3E08,0x0008,
0xFFFF,
0xFFFF,
};
const unsigned int p3gkf[] = {
100, // 夕焼け小焼け
0x0004,0x3704,0x3704,0x3704,0x3704,0x3704,0x3704,0x3704,
0x3404,0x3404,0x3204,0x3004,0x0004,0x3504,0x3404,0x3204,
0x0004,0x3704,0x3704,0x3704,0x0004,0x3504,0x3504,0x3504,
0x0004,0x3704,0x3704,0x3704,0x2D04,0x3704,0x2D04,0x0004,
0x2908,0x3504,0x0004,0x2808,0x3408,0x3008,0x3708,0x3008,
0x3708,0x3404,0x3704,0x3504,0x3404,0x3204,0x3504,0x3404,
0x3204,0x3004,0x3404,0x3504,0x3704,0x300C,0x0004,
0xFFFF,
100, // フランスの古い歌
0x0004,0x3A04,0x3C04,0x3E04,0x3C04,0x3A10,0x3F08,0x3C08,
0x3A08,0x3708,0x3F08,0x3C08,0x3A08,0x3708,0x3C10,0x3A08,
0x2B08,0x3A04,0x3C04,0x3E04,0x3C04,0x3A10,0x3F08,0x3C08,
0x3A08,0x3708,0x3F08,0x3C08,0x3A08,0x3708,0x3C10,0x3A10,
0x2804,0x3704,0x3A04,0x3F04,0x3004,0x3704,0x3C04,0x3F04,
0x2804,0x3704,0x3A04,0x3F04,0x3204,0x3A04,0x3C04,0x4204,
0x3A04,0x3E04,0x4304,0x0004,0x3F10,0x4308,0x0008,0x3E08,
0x3208,0x3A04,0x3C04,0x3E04,0x3C04,0x3A10,0x3F08,0x3C08,
0x3A08,0x3708,0x3F08,0x3C08,0x3A08,0x3708,0x3708,0x3604,
0x3210,
0xFFFF,
80, // かたつむり
0x3204,0x3904,0x3204,0x3904,0x3604,0x3904,0x3604,0x3904,
0x3204,0x3904,0x3704,0x3604,0x3904,0x2D04,0x3904,0x0004,
0x3204,0x2604,0x3204,0x2604,0x3204,0x2604,0x3204,0x2604,
0x2D04,0x3904,0x2D04,0x3904,0x3204,0x3904,0x3E04,0x0004,
0x3204,0x3604,0x3904,0x3E04,0x3204,0x3604,0x3904,0x3E04,
0x3604,0x3204,0x3904,0x2D04,0x3204,0x3604,0x3204,0x0004,
0xFFFF,
0xFFFF,
};
const unsigned int p4gkf[] = {
80, // 夕焼け小焼け
0x3010,0x3010,0x3404,0x3404,0x3204,0x3004,0x2B10,0x3010,
0x3010,0x2B10,0x0010,0x4806,0x4A02,0x4804,0x4504,0x4804,
0x4804,0x4304,0x4304,0x0010,0x0010,0x4304,0x4004,0x3E04,
0x3C04,0x3E04,0x3E04,0x3C04,0x3E04,0x0010,0x480C,0x0004,
0xFFFF,
80, // フランスの古い歌
0x0004,0x3710,0x3708,0x3708,0x3710,0x3708,0x3708,0x3710,
0x3708,0x3708,0x3608,0x3208,0x3708,0x2B08,0x3710,0x3708,
0x3708,0x3710,0x3708,0x3708,0x3710,0x3708,0x3708,0x3508,
0x3208,0x3710,0x0010,0x0010,0x0010,0x0010,0x3704,0x000C,
0x3C10,0x3E08,0x0008,0x0010,0x3710,0x3708,0x3708,0x3710,
0x3708,0x3708,0x3710,0x3708,0x3708,0x3008,0x3208,0x2B10,
0xFFFF,
0,0xFFFF, // かたつむり
0xFFFF,
};