Longan Nanoを使ってみる 15 ～音楽を鳴らす～

# 前の記事
[Longan Nanoを使ってみる 14 ～PWMとサウンド～](https://qiita.com/BUBUBB/items/3b592078d30cfed0ec53)

# 全体の目次
[Longan Nanoを使ってみる 1 ～ビルド環境の構築～](https://qiita.com/BUBUBB/items/7ce85ada67a3f6d1944d)
[Longan Nanoを使ってみる 2 ～デバッガの環境設定～](https://qiita.com/BUBUBB/items/864b2ef9404453810d5f)
　[Sipeed RISC-V Debugger](https://qiita.com/BUBUBB/items/61316a4df1d9f81e2864)
[Longan Nanoを使ってみる 3 ～デバッガの使用方法～](https://qiita.com/BUBUBB/items/8efa5989e7f16f67672e)
[Longan Nanoを使ってみる 4 ～printfを使ったデバッグ～](https://qiita.com/BUBUBB/items/b607a3eedc4b66e7a74f)
[Longan Nanoを使ってみる 5 ～ゲームのプロジェクトを作成～](https://qiita.com/BUBUBB/items/98d9c25120c0955d5200)
[Longan Nanoを使ってみる 6 ～文字出力～](https://qiita.com/BUBUBB/items/8dee2eabe78f38364674)
　[Longan Nanoを使ってみる ～FONTX2ファイルを作る～](https://qiita.com/BUBUBB/items/8760ac886423bf06be68)
[Longan Nanoを使ってみる 7 ～外枠とブロックを書く～](https://qiita.com/BUBUBB/items/96abb1158b413c9efd27)
　[Longan Nanoを使ってみる ～謎の画像表示関数～](https://qiita.com/BUBUBB/items/40b99468839e1daeebfb)
[Longan Nanoを使ってみる 8 ～ボールを動かす～](https://qiita.com/BUBUBB/items/92f4816af3c41ea13b9e)
[Longan Nanoを使ってみる 9 ～A/Dコンバータから入力～](https://qiita.com/BUBUBB/items/311c179f7d3a0e30495d)
[Longan Nanoを使ってみる 10 ～パドルを動かす～](https://qiita.com/BUBUBB/items/eb8149c68f6e5456e7ca)
[Longan Nanoを使ってみる 11 ～ボタンの入力](https://qiita.com/BUBUBB/items/2445940a6b0d2110ee04)
[Longan Nanoを使ってみる 12 ～ボールのロス～](https://qiita.com/BUBUBB/items/f8c6ff860de127c93cb9)
[Longan Nanoを使ってみる 13 ～ステージの遷移とゲームオーバー～](https://qiita.com/BUBUBB/items/f1ae81b835f55abe319e)
[Longan Nanoを使ってみる 14 ～PWMとサウンド～](https://qiita.com/BUBUBB/items/3b592078d30cfed0ec53)
[Longan Nanoを使ってみる 15 ～音楽を鳴らす～](https://qiita.com/BUBUBB/items/adb35e02209ad78dfa9b)
[Longan Nanoを使ってみる 16 ～とりあえずのまとめ～](https://qiita.com/BUBUBB/items/ba782ccc80f50b7ab8c3)

# はじめに
一般的なゲームでは、ゲームの区切りなどで音楽が鳴る。特に、レトロゲームではゲームオーバー時に葬送行進曲が流れないのはバグである、と言っても良い。

今回は、サウンド機能を使用して、12音階を演奏できるように変更する。

音楽を鳴らしながらゲームを続けるのは結構面倒なので、効果音と違ってメロディについては、その間、プログラムはブロックされるものとする。


# 注意
このページは、quiita.com で公開されています。URLがqiita.com以外のサイト、例えばjpdebug.comなどのページでご覧になっている場合、悪質な無許可転載サイトで記事を見ています。正しい記事は、https://qiita.com/BUBUBB/items/7ce85ada67a3f6d1944d からリンクしています。

無許可転載サイトでの権利表記（CC BY SA 2.5、CC BY SA 3.0とCC BY SA 4.0など）は、不当な表示です。
正確な内容は、qiitaのページで参照してください。


# 音程について
音楽については全く門外漢だが、12音階（いわゆるドレミ、12平均律）は数学的なルールで作られている。
ド＃、レ、レ＃、ミ・・・ラ、ラ＃、シ、ドとピアノの鍵盤を白黒全部並べると、１オクターブが１２音となる。この音は、隣同士の比が、２の１２乗根になる。つまり、「前の音 × <sup>12</sup>√２」が次の音になる。　いわゆる「等比数列」。

ドの周波数 × <sup>12</sup>√２ = ド＃の周波数　。ド＃の周波数 ×  <sup>12</sup>√２ =　レの周波数。

<sup>12</sup>√２ とは、2 <sup>1/12</sup>のことのなので、Excelなどを使えば、=上のセル*2^(1/12)として、１２音の周波数を計算できる。例えば、基準周波数を261.63とすると、次のような計算になる。この基準周波数は、440Hzを「ラ」としたときの「ド」の音で、「中央ハ」と呼ぶ。これは決め事なのでそういうルールだと覚えるしかないし、そもそもマイコンとは違う世界の話。

※ 実際のところ、この値を変えても、きちんとドレミに聞こえてくる。絶対音感とやらの持ち主は、440Hzのラ以外はラではないらしいが。

<img width="50%" alt="sound1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2096509/6f20f23a-b80e-e527-5370-a9c4052da3f6.png">



また、１オクターブが１２音で、それぞれが<sup>12</sup>√２なので、1オクターブ上がると、前の音から周波数は二倍になる。（低いドの周波数の２倍が高いド）

１２乗根の計算をリアルタイムで行うのは負荷が高そうなので、１２音を事前に計算しておき、テーブルにしたうえで、オクターブはその計算をもとに倍々にしていく。

このプログラムでは、2KHzへの分周比を使って音程を指定する。求められた周波数から分周比を求める。261.63Hzのドが基準だと、音が高すぎるので、もっと低い音を基準とする。なので、65.406のド（中央ハ、261.63Hzから２オクターブ下）を基準にすると、次のようになる。

||周波数|分周比|
|---|---|---|
|ド　　C|65.406|3057.823441
|ド＃　C#|69.29524315|2886.200999
|レ　　D|73.41575273|2724.210984
|レ＃　D#|77.78128056|2571.31277
|ミ　　E|82.40639619|2426.996074
|ファ　F|87.3065355|2290.779251
|ファ＃F#|92.49805226|2162.207691
|ソ　　G|97.99827267|2040.852298
|ソ＃　G#|103.8255532|1926.30806
|ラ　　A|109.9993419|1818.192696
|ラ＃　A#|116.5402431|1716.145382
|シ　　B|123.4700866|1619.825543

これを配列にしておき、オクターブが違う場合にはこれを２倍、４倍としていけばよい。２倍、４倍はマイコンにとってはビットシフトで実現できる簡単な操作になる。

# サウンドの出し方

出力する分周比をそのままプログラムに書くと、デバッグもやりにくいので、簡単な文字列で音程、オクターブ、音の長さを指定する。Cをド、cをド＃…とし、音、オクターブ、音の長さを３桁の整数として表現する。また、空白文字を休符として音の区切りとする。
例："B1400 B1300 B1100 B1400 D2300 c2100 c2300B1100 B1300 a1100 B1400"
という感じでメロディーを指定する。
B1400は、B（シ）、オクタープは2<sup>1</sup>=２倍の周波数、長さ400msとなる。

また、休符の長さはメロディによりさまざまなので、空白文字の数と、さらに空白文字１つの待ち時間を指定できるようにする。

ついでに、デバッグなどに使えるように、「プー」となるだけの関数も作っておく。

SoundPlay(char *Snd,int waitCnt)が、メロディを演奏する関数で、与えられた文字列を先頭から読み、処理を行う。

`int Freq = FreqTbl[idx] / (1 << (int)TOVAL(S2));`が、分周比を作っているところで、周波数が２倍になる、ということは分周比は1/2になるということなので、２倍の周波数だと、１を左に１シフト（２になる）したもので割っている。
例えば、65.4が基準周波数だと、分周比は200,000/65.4 = 3058となる。周波数が２倍になる（130.8Hz)と、分周比は200,000/130.8で、1529。周波数が倍になると、分周比は1/2になる。

#### sound.h
```
void Beep(int ms);
void SoundPlay(char *Snd,int waitCnt);
```

#### sound.c
```
   :
   :
// 指定した時間、ビープ音を鳴らす
void Beep(int ms)
{
    SoundSet(800);
    StartSound();
    delay_1ms(ms);
    StopSound();
}


//                     C   C#    D    D#   E    F   F#   G    G#    A    A#   B  
const int FreqTbl[]={3058,2886,2724,2571,2427,2291,2162,2041,1926,1818,1716,1620};
const char* sndChar =  "CcDdEFfGgAaB";
#define TOVAL(__C__) ((__C__) - '0')

void SoundPlay(char *Snd,int waitCnt)
{

    char* pWork = Snd;
    while (*pWork != 0) {
        char S1 = *pWork++;
        if (S1 == ' ') {
            delay_1ms(waitCnt);
            continue;
        }
        char S2 = *pWork++;
        int idx = strchr(sndChar,(int)S1) - sndChar;
        int Freq = FreqTbl[idx] / (1 << (int)TOVAL(S2));
        int len = TOVAL(*pWork)*100 + TOVAL(*(pWork+1)) *10 +  TOVAL(*(pWork+2));
        pWork+=3;
        SoundSet(Freq);
        StartSound();
        delay_1ms(len);
        StopSound();
    }
}
   :
   :

```

# ゲームからメロディを鳴らす

このSouldPlayは、呼び出すと音楽が終わるまで戻ってこない。（ブロックされる）そのため、ゲームに動きがない場合にのみ使用できる。

ゲームオーバー時と、ステージクリアの時に、サウンドを鳴らしてみる。STATE_NEXTSTAGEと、STATE_GAMEOVERに入った最初の回に、メロディを演奏する。また、音楽演奏が入る分、待ち時間を減らす。

#### game.c

```
            case STATE_NEXTSTAGE:{
                static u16 waitNextCnt = 0;

                if (waitNextCnt == 0) {
                    // 画面に"WELL DONE!"と表示させる
                    LCD_ShowString(5,80,(const unsigned char *)"WELL DONE!",WHITE);
                    // 一定時間が経過するか、ボタンが押されたらブロックを初期化してゲームの再開に遷移する
				    SoundPlay("E3200 E3200 F3200 G3200 G3200 F3200 E3200 D3200 C3200 C3200 D3200 E3200 D3300 C3100 C3400",10);	
                }
                waitNextCnt++;
                if (waitNextCnt == 0x10 || CheckP1Button()) {
                    LCD_Clear(BLACK);
                    waitNextCnt = 0;
                    Stage++;
                    InitBlock(Stage);                    
                    gameState = STATE_STARTGAME;
                }
               break;                
            }
                 :
                 :
                 :
            case STATE_GAMEOVER:{
                /*ゲーム－オーバー処理*/
                static u16 waitGameOverCnt = 0;
                if (waitGameOverCnt == 0) {
                    // 画面に"-- MISS! --"と表示させる
                    LCD_ShowString(5,80,(const unsigned char *)"-GAMEOVER-",WHITE);
                    // 一定時間が経過するか、ボタンが押されたらゲームの再開に遷移する
				    SoundPlay("B1400 B1300 B1100 B1400 D2300 c2100 c2300B1100 B1300 a1100 B1400",10);						
                }
                waitGameOverCnt++;
                if (waitGameOverCnt == 0x5 || CheckP1Button()) {
                    LCD_Clear(BLACK);
                    waitGameOverCnt = 0;
                    gameState = STATE_IDLE;
                }                
                break;
            }
 
```


# ここまででできたこと

サウンドを使用して、メロディーを演奏できるようにした。ゲームの状態遷移時に音楽を鳴らすようにした。

# 次の記事に進む

[Longan Nanoを使ってみる 16 ～とりあえずのまとめ～](https://qiita.com/BUBUBB/items/ba782ccc80f50b7ab8c3)

# 今回の追加を反映したソースコード

#### sound.h

```
#ifndef __sound_h__
#define __sound_h__

void sound_pwm_init();
void StartSound(void);
void StopSound(void);
void SoundSet(int Period);

void BallSoundInit(void);
void BallSoundStart(int sndNo);
void BallSoundStop(void);
void BallSoundTick(void);
void Beep(int ms);
void SoundPlay(char *Snd,int waitCnt);
#endif
```

#### sound.c

```
#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "sound.h"

void sound_pwm_init()
{
  	timer_oc_parameter_struct timer_ocinitpara; 
	timer_parameter_struct timer_initpara;           // タイマーパラメータ構造体の宣言
	timer_deinit(TIMER4);
    timer_struct_para_init(&timer_initpara);


   	// rcu_periph_clock_enable(RCU_AF);
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
    rcu_periph_clock_enable(RCU_TIMER4);

    timer_initpara.prescaler         = 539;                 // タイマーの16ビットプリスケーラには54MHzが入力されるので、54Mhz/540・・・
                                                            // のはずだが、108MHzが入力されるように振舞っている。
                                                            // なので108MHz/540 で、およそ200KHzが得られるハズ。
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;  // count alignment edge = 0,1,2,3,0,1,2,3... center align = 0,1,2,3,2,1,0
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;    // Counter direction
    timer_initpara.period            = 3;		            // 200KHz/4 で、おおよそ50KHzが得られるハズ。ただ、これはSoundSetで動的に書き換えられちゃうけどね。
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;    // This is used by deadtime, and digital filtering (not used here though)
    timer_initpara.repetitioncounter = 0;                   // Runs continiously	
    timer_init(TIMER4, &timer_initpara);  					// Apply settings to timer   

 	timer_channel_output_struct_para_init(&timer_ocinitpara);
    timer_ocinitpara.outputstate  = TIMER_CCX_ENABLE;        // Channel enable
    timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;      // Disable complementary channel
    timer_ocinitpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;  // Active state is high
    timer_ocinitpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;  
    timer_ocinitpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW; // Idle state is low
    timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    timer_channel_output_config(TIMER4, TIMER_CH_3,&timer_ocinitpara);   // Apply settings to channel

    timer_channel_output_pulse_value_config(TIMER4,TIMER_CH_3,0);                   // Set pulse width
    timer_channel_output_mode_config(TIMER4,TIMER_CH_3,TIMER_OC_MODE_PWM0);         // Set pwm-mode
    timer_channel_output_shadow_config(TIMER4,TIMER_CH_3,TIMER_OC_SHADOW_DISABLE);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER4);

    /* start the timer */
    timer_enable(TIMER4);
    
}

bool 	isSound = TRUE;
void StartSound(void) {
    if (isSound == FALSE) return;
    timer_enable(TIMER4);		
}
void StopSound(void) {
    if (isSound == FALSE) return;
    timer_disable(TIMER4);		
}
void SoundSet(int Period)
{
    TIMER_CH3CV(TIMER4) = Period/2;	   
    TIMER_CAR(TIMER4) =  Period;	 	
}


static bool bSound[2];          // ボールの音が出てるかのフラグ。[0]がポピ、[1]がピポ。
static int iSoundIdx=0;         // ボールの音がどこまで進んでいるか。
                                // 例えば「ポピ」を出しているなら、0でポ、1がピ。
static int iSoundStep[2][2];    // 今出している音の進行状況。例えば、「ポピ」の時であれば、
                                // [0][0]が「ポ」の間増やされていき、一定の値になったら
                                //「ピ」に変えて[0][1]の値を増やしていく
static int iSoundTim[2][2] = {{400,800},{1000,600}};		//200Hzに対して、これで分週する。400なら、500Hzになる。


void BallSoundInit(void)
{
    // 変数を初期化して
	for (u8 i = 0;i<2;i++) {
		iSoundIdx = 0;
		bSound[i] = FALSE;
		iSoundStep[i][0] = iSoundStep[i][1] = 0;
	}
	timer_disable(TIMER4);		//音を止める
}
// 音を出し始める。引数に音のインデックス「ピポ」「ポピ」を指定
void BallSoundStart(int sndNo)
{
	BallSoundStop();
	bSound[sndNo] = TRUE;

}
// 今出ている音をすべて止める。初期化と同じ処理でよい
void BallSoundStop(void)
{
    BallSoundInit();
}


// 音を出す主処理。タイマーで一定間隔に呼び出される必要がある
void BallSoundTick(void)
{
	for (u8 i = 0 ; i < 2;i++){
		if (bSound[i]) {
			if (iSoundStep[i][iSoundIdx] == 0) {		// 音のなり始め
				SoundSet(iSoundTim[i][iSoundIdx]);
				timer_enable(TIMER4);
				iSoundStep[i][iSoundIdx]++;					
			} else if (iSoundStep[i][iSoundIdx]<3) {
				iSoundStep[i][iSoundIdx]++;
			} else if (iSoundStep[i][iSoundIdx] == 3) {	// 次の音へ
				if (iSoundIdx == 1) {			//　全部の音を出し終わったら終了
					BallSoundStop();
				} else {
					iSoundStep[i][iSoundIdx] = INT16_MAX;
					iSoundIdx++;
				}
			}
		}
	}

}

// 指定した時間、ビープ音を鳴らす
void Beep(int ms)
{
    SoundSet(800);
    StartSound();
    delay_1ms(ms);
    StopSound();
}

//渡された文字列に基づいてメロディーを演奏する
//
const int FreqTbl[]={3058,2886,2724,2571,2427,2291,2162,2041,1926,1818,1716,1620};
const char* sndChar =  "CcDdEFfGgAaB";
#define TOVAL(__C__) ((__C__) - '0')

void SoundPlay(char *Snd,int waitCnt)
{

    char* pWork = Snd;
    while (*pWork != 0) {
        char S1 = *pWork++;
        if (S1 == ' ') {
            delay_1ms(waitCnt);
            continue;
        }
        char S2 = *pWork++;
        int idx = strchr(sndChar,(int)S1) - sndChar;
        int Freq = FreqTbl[idx] / (1 << (int)TOVAL(S2));
        int len = TOVAL(*pWork)*100 + TOVAL(*(pWork+1)) *10 +  TOVAL(*(pWork+2));
        pWork+=3;
        SoundSet(Freq);
        StartSound();
        delay_1ms(len);
        StopSound();
    }
}
```

#### game.c

```
#include "lcd/lcd.h"
#include "led.h"
#include "memory.h"
#include "gd32vf103.h"
#include "game.h"
#include "ball.h"
#include "block.h"
#include "paddle.h"
#include "button.h"
#include "sound.h"

enum GAMESTATE gameState;	    //　ゲームの状態
volatile u8 WakeFlag = 0;       // このフラグが１になると、処理が開始される

int LifeCnt = 0;
int Score = 0;
int Stage= 1;                      // ステージ

//
// 割り込みハンドラ。タイマーにより指定した周期で非同期に呼び出される
//
void TIMER5_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_FLAG_UP)){
        timer_interrupt_flag_clear(TIMER5, TIMER_INT_FLAG_UP);
    	WakeFlag = 1;
	    
        static u8 oeFlag;
        if (oeFlag == 0) {
            gpio_bit_reset(GPIOB, GPIO_PIN_8); //OE#
            oeFlag = 1;
        } else {
            gpio_bit_set(GPIOB, GPIO_PIN_8); //OE#
            oeFlag = 0;
        }
    }
}

//
// タイマーの初期化
//
void timer5_config(int Cnt)
{
    // タイマーのパラメータを設定する。
	// タイマーの16ビットプリスケーラには54MHzが入力される・・・はずだが、108MHzが入力されるように振舞っている。
	// それを、10000分周(timer_initpara.prescaler = 10000-1)すると、おおよそ108,000,000/10000= 10.8Khz
    // それを、引数の cnt回数えて(timer_initpara.period = Cnt;)タイマーの周期を決める。
	// 例えば、cntが30の時は、10,800 / 30 =360で、360Hzとなる。
    timer_parameter_struct timer_initpara;
    rcu_periph_clock_enable(RCU_TIMER5);
    timer_deinit(TIMER5);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 10000 - 1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = Cnt;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER5, &timer_initpara);
    timer_auto_reload_shadow_enable(TIMER5);
    timer_interrupt_enable(TIMER5, TIMER_INT_UP);

    // 割り込みを有効にして、タイマー５を設定する
    eclic_global_interrupt_enable();
	eclic_set_nlbits(ECLIC_GROUP_LEVEL3_PRIO1);
	eclic_irq_enable(TIMER5_IRQn,1,0);
    
    // タイマーを開始する
    timer_enable(TIMER5);
}



//　外枠とスコア、ライフ残を表示する
void DrawBORDER()
{
	LCD_DrawLine(GAMEAREA_X0,GAMEAREA_Y1,GAMEAREA_X0,GAMEAREA_Y0,WHITE);
	LCD_DrawLine(GAMEAREA_X0,GAMEAREA_Y0,GAMEAREA_X1,GAMEAREA_Y0,WHITE);
	LCD_DrawLine(GAMEAREA_X1,GAMEAREA_Y0,GAMEAREA_X1,GAMEAREA_Y1,WHITE);

	LCD_ShowString(0,0,(const u8 *)"SCORE:",WHITE);
	LCD_ShowString(10,160-12,(const u8 *)"LIFE:",WHITE);
	char life[3];
	sprintf(life,"%1d",LifeCnt);
	LCD_ShowString(55,160-12,(u8 *)life,WHITE);
	u8 scr[12];
	sprintf((char *)scr,"%5d0",Score);
	LCD_ShowString(38,0,scr,WHITE);	
}


// 座標が、矩形の外側に対して、どの象限にいるのかを返す関数
//    1         2        3
//        +---------+
//   4    |    5    |    6
//        +---------+
//   7         8         9    
unsigned char GetOrthant(int x , int y , int x1, int y1 , int x2 , int y2)
{
	bool bLowerX1 =  (x < x1);
	bool bUpperX2 =  (x > x2);
	bool bLowerY1 =  (y < y1);
	bool bUpperY2 =  (y > y2);

    
	if (bLowerX1) {
        return bLowerY1 ? 1: (bUpperY2 ? 7:4);
	} else if (bUpperX2) {
        return bLowerY1 ? 3: (bUpperY2 ? 9:6);
	} else {
        return bLowerY1 ? 2: (bUpperY2 ? 8:5);
	}
}


//
// メイン処理
//
void Game(bool isDemo) 
{
    rcu_periph_clock_enable(RCU_GPIOB);
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);	// B8をデバッグに使う

    // 初期化処理
    gameState = STATE_INIT;             // ステータスを初期化にする
    timer5_config(100);                  // タイマーの初期化を行う
    LCD_Init();
    LCD_Clear(BLACK);
	Adc_init();
    sound_pwm_init();

    BallSoundInit();                    // ボール音の初期化


    u16 tick = 0;                       // LEDを点滅させるためのカウンターを初期化
    gameState = STATE_IDLE;             //初期化処理が終了したのでゲーム開始処理を行う
    static u16 heartBeat = 0;            // 状態遷移ループのカウンタ
    
    while (TRUE) { 
		timer_enable(TIMER5);               // タイマーを有効にする
        // タイマーのウェイト処理。wakeFlagが割り込みルーチン内で１になるまで無限ループする
		
        while(WakeFlag == 0) {
            delay_1ms(10);
            break;
        }

        tick = (tick + 1) & 0x8FFF;     // LEDの点滅用カウンタのインクリメント
		WakeFlag = 0;                   // タイマーのウエイトフラグを初期化する

        heartBeat = (heartBeat+1) & 0x7FFF;
        
        BallSoundTick();                // サウンド処理を実行
        switch (gameState) {
            case STATE_IDLE:{
                u16 cnt = heartBeat & 0xFF;
                if (cnt == 0x00) {
                    LCD_ShowString(5,40,(const unsigned char *)"PUSH BUTTON",WHITE);
                    LCD_ShowString(5,60,(const unsigned char *)" TO START  ",WHITE);
                } else if (cnt == 0x80) {
                    LCD_ShowString(5,40,(const unsigned char *)"PUSH BUTTON",RED);
                    LCD_ShowString(5,60,(const unsigned char *)" TO START  ",RED);
                }
                if (CheckP1Button()) {
                    LCD_Clear(BLACK);
                    Score = 0;
                    LifeCnt = 3;
                    Stage = 1;
                    InitBlock(Stage);
                    gameState = STATE_STARTGAME;
                }
                break;
            }
            case STATE_STARTGAME:{
                /*ゲームの開始処理 */

                DrawBORDER();               //外枠とライフ残、スコアを画面に表示させる
                DrawBlock();
                InitBallPos(0,NULL);
				InitPaddle();
                gameState = STATE_INGAME;
                break;
            }
            case STATE_INGAME:{
        		drawDeleteBall(FALSE);			// ひとつ前のボールを消す
	            u8 ret= moveBall();
	            if (ret == 0) {                 // すべてのボールがなくなったら
                    LifeCnt--;
                    gameState = STATE_BALLLOSS; // ボールロスの状態に遷移させる
                    break;
                } else if (ret == 2) {          // ブロックがすべてなくなったら
                    gameState = STATE_NEXTSTAGE;
                    break;
                }
        		drawDeleteBall(TRUE);			// ひとつ前のボールを消す
				breakout_PaddleCtrl(isDemo);			// パドルを動かす
                break;                
            }
            case STATE_NEXTSTAGE:{
                static u16 waitNextCnt = 0;

                if (waitNextCnt == 0) {
                    // 画面に"WELL DONE!"と表示させる
                    LCD_ShowString(5,80,(const unsigned char *)"WELL DONE!",WHITE);
                    // 一定時間が経過するか、ボタンが押されたらブロックを初期化してゲームの再開に遷移する
				    SoundPlay("E3200 E3200 F3200 G3200 G3200 F3200 E3200 D3200 C3200 C3200 D3200 E3200 D3300 C3100 C3400",10);	
                }
                waitNextCnt++;
                if (waitNextCnt == 0x10 || CheckP1Button()) {
                    LCD_Clear(BLACK);
                    waitNextCnt = 0;
                    Stage++;
                    InitBlock(Stage);                    
                    gameState = STATE_STARTGAME;
                }
               break;                
            }
            case STATE_BALLLOSS:{               // ボールロス
                static u16 waitCnt = 0;

                // ライフがないならゲームオーバーに遷移
                if (LifeCnt == 0) {
                    gameState = STATE_GAMEOVER;
                    break;
                }
                // 画面に"-- MISS! --"と表示させる
                if (waitCnt == 0) {
                    LCD_ShowString(5,80,(const unsigned char *)"-- MISS! --",WHITE);
                    SoundSet(800);
                    StartSound();
                }
                // 一定時間が経過するか、ボタンが押されたらゲームの再開に遷移する
                waitCnt++;
                if (waitCnt == 0x100 || CheckP1Button()) {
                    LCD_Clear(BLACK);
                    waitCnt = 0;
                    StopSound();
                    gameState = STATE_STARTGAME;
                }
               break;
            }
            case STATE_GAMEOVER:{
                /*ゲーム－オーバー処理*/
                static u16 waitGameOverCnt = 0;
                if (waitGameOverCnt == 0) {
                    // 画面に"-- MISS! --"と表示させる
                    LCD_ShowString(5,80,(const unsigned char *)"-GAMEOVER-",WHITE);
                    // 一定時間が経過するか、ボタンが押されたらゲームの再開に遷移する
				    SoundPlay("B1400 B1300 B1100 B1400 D2300 c2100 c2300B1100 B1300 a1100 B1400",10);						
                }
                waitGameOverCnt++;
                if (waitGameOverCnt == 0x5 || CheckP1Button()) {
                    LCD_Clear(BLACK);
                    waitGameOverCnt = 0;
                    gameState = STATE_IDLE;
                }                
                break;
            }
        }

    }
}
```