2022年5月1日日曜日

SN76489ANの使い方


SN76489ANの使い方


注意:この記事の内容を鵜呑みにし、事故や損失を招いた場合でも当方は一切の責任は負いかねます。自己責任でお願いします。

SN76489ANは、矩形波3チャンネル + ノイズ音1チャンネルの音源IC(DCSG)です。


SN76489AN  (台湾製)






・SN76489ANの特徴


・矩形波3チャンネル + ノイズ音1チャンネルの出力
・16ピンDIP
・ハードウェアエンベロープ : 無
・アッテネータ : 0~28dB (4bit値で指定)


音源ICとしては少ピンな部類です。
レジスタの種類も少なく制御がとても簡単なので、簡単な効果音を鳴らしたい時におすすめです。



・SN76489ANのレジスタ

Texas Instrumentsのデータシートに記載されているデータピン番号は、MSB側から割り振られているため、注意してください。

下表の括弧内()は、LSBから番号を振りなおして機能を考えたピン名称です。(データシート上に記載されていません。)

アドレス

D0(mode) = H

D1(AA2),D2(AA1),D3(AA0)
ピンでアドレスを指定します。
レジスタ名 機能
$0 Tone 1 Frequency トーン1の音程を決定します。

このレジスタへのデータの書き込みは、2段階必要となります。

・第一バイト
D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1000n3n2n1n0

・第二バイト
D0
(mode)
D1
(AA2)
D2
(DD9)
D3
(DD8)
D4
(DD7)
D5
(DD6)
D6
(DD5)
D7
(DD4)
0-n9n8n7n6n5n4

出力する周波数fは、
   f = CLK / (32 * n)   [Hz]
   CLK : 
SN76489ANの入力クロック周波数
   n : このレジスタ値(n9(MSB)~n0(LSB)の10bit)
となります。
$1
Tone 1 Attenuation トーン1の音量を決定します。
0~28dBアッテネートできます。

D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1001na3na2na1na0

アッテネートレベルAは、
   A = 2 * na   [dB]
   na : このレジスタ値(na3(MSB)~na0(LSB)の4bit)
   
   ただし、na = 15(0xF)のとき、ミュート
となります。
$2 Tone 2 Frequency
トーン2の音程を決定します。

このレジスタへのデータの書き込みは、2段階必要となります。

・第一バイト
D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1010n3n2n1n0

・第二バイト
D0
(mode)
D1
(AA2)
D2
(DD9)
D3
(DD8)
D4
(DD7)
D5
(DD6)
D6
(DD5)
D7
(DD4)
0-n9n8n7n6n5n4

出力する周波数fは、
   f = CLK / (32 * n)   [Hz]
   CLK : 
SN76489ANの入力クロック周波数
   n : このレジスタ値(n9(MSB)~n0(LSB)の10bit)
となります。
$3 Tone 2 Attenuation トーン2の音量を決定します。
0~28dBアッテネートできます。

D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1011na3na2na1na0

アッテネートレベルAは、
   A = 2 * na   [dB]
   na : このレジスタ値(na3(MSB)~na0(LSB)の4bit)
   
   ただし、na = 15(0xF)のとき、ミュート
となります。
$4 Tone 3 Frequency トーン3の音程を決定します。

このレジスタへのデータの書き込みは、2段階必要となります。

・第一バイト
D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1100n3n2n1n0

・第二バイト
D0
(mode)
D1
(AA2)
D2
(DD9)
D3
(DD8)
D4
(DD7)
D5
(DD6)
D6
(DD5)
D7
(DD4)
0-n9n8n7n6n5n4

出力する周波数fは、
   f = CLK / (32 * n)   [Hz]
   CLK : 
SN76489ANの入力クロック周波数
   n : このレジスタ値(n9(MSB)~n0(LSB)の10bit)
となります。
$5 Tone 3 Attenuation トーン3の音量を決定します。
0~28dBアッテネートできます。

D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1101na3na2na1na0

アッテネートレベルAは、
   A = 2 * na   [dB]
   na : このレジスタ値(na3(MSB)~na0(LSB)の4bit)
   
   ただし、na = 15(0xF)のとき、ミュート
となります。
$6 Noise Control ノイズコントロールレジスタです。

D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1 1 1 0 - bit2 bit1 bit0

bit0, bit1でノイズジェネレータのクロック源を選択します。
bit0 = H, bit1 = Lの場合、トーン3の音程レジスタの値でノイズ音程が決まります。
bit1 bit0 機能
0 0 高ピッチノイズ
0 1ピッチノイズ
1 0 ピッチノイズ
1 1 トーン3の音程レジスタ

bit2
  L : 周期的ノイズ
  H : ホワイトノイズ

LSBから数えてbit0,1がノイズ周波数を決めて、bit2がノイズの種類を決めます。

ノイズ周波数は基本的に、高・中・低の3種類のみです。
トーン3の出力を犠牲もしくは、音程レジスタを共用することで、ノイズ周波数を自由に決めることができます。
$7 Noise Attenuation ノイズの音量を決定します。
0~28dBアッテネートできます。

D0
(mode)
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)
1111na3na2na1na0

アッテネートレベルAは、
   A = 2 * na   [dB]
   na : このレジスタ値(na3(MSB)~na0(LSB)の4bit)
   
   ただし、na = 15(0xF)のとき、ミュート
となります。

 




・ピン配置





ピン番号 名称 I/O 機能
1 D2
(AA1,DD9)
I
ポートピンです。

D0(mode) = Hのとき、D2(AA1)
内部レジスタのアドレス指定ピンとなります。

D0(mode) = Lのとき、D2(DD9)
トーンデータ一時ラッチレジスタピンとなります。
2 D1
(AA2)
I ポートピンです。

D0(mode) = Hのとき、D1(AA2)
内部レジスタのアドレス指定ピンとなります。
3 D0
(mode)
I ポートピンです。
ポートレジスタの機能を選択します。


・D0(mode) = Hのとき

アドレス
MSB
アドレス
LSB
データMSBデータLSB
D0
(mode)
 = H
D1
(AA2)
D2
(AA1)
D3
(AA0)
D4
(DD3)
D5
(DD2)
D6
(DD1)
D7
(DD0)

D0(mode) = Hのとき、
D1(AA2)~D3(AA0)がアドレス指定レジスタ、D4(DD3)~D7(DD0)がデータレジスタとなります。


・D0(mode) = Lのとき


データMSB
データLSB
D0
(mode)
 = L
   -   D2
(
DD9)
D3
(DD8)
D4
(DD7)
D5
(DD6)
D6
(DD5)
D7
(DD4)

D0(mode) = Lのとき、
D2(DD9)~D7(DD4)が音程データレジスタとなります。
このモードで書き込みすると、以前書き込んだ音程値(DD0~DD3)とともに内部の音程レジスタ($0, $2, $4)へ値が書き込まれます。

4 READY O この端子がLを出力しているとき、「SN76489AN」がビジー状態であること示します。

この端子はオープンコレクタと出力のため、プルアップ抵抗が必要です。
5 ~WE I ライトイネーブルピンです。

「~WE」がLでかつ、「~CE」が
Lの期間にD7~D0の状態がラッチされます。
6 ~CE I チップイネーブルピンです。

「~WE」がLでかつ、「~CE」が
Lの期間にD7~D0の状態がラッチされます。
7 AOUDIO
OUT
O 音声出力端子です。
8 GND - グランドです。
9 N.C. - 無接続
10 D7
(DD0,DD4)
I ポートピンです。

D0(mode) = Hのとき、D7(DD0)
データピンとなります。

D0(mode) = Lのとき、D7(DD4)
トーンデータ一時ラッチレジスタピンとなります。
11 D6
(DD1,DD5)
I ポートピンです。

D0(mode) = Hのとき、D6(DD1)
データピンとなります。

D0(mode) = Lのとき、D6(DD5)
トーンデータ一時ラッチレジスタピンとなります。
12 D5
(DD2,DD6)
I ポートピンです。

D0(mode) = Hのとき、D5(DD2)
データピンとなります。

D0(mode) = Lのとき、D5(DD6)
トーンデータ一時ラッチレジスタピンとなります。
13 D4
(DD3,DD7)
I ポートピンです。

D0(mode) = Hのとき、D4(DD3)
データピンとなります。

D0(mode) = Lのとき、D4(DD7)
トーンデータ一時ラッチレジスタピンとなります。
14 CLOCK I クロック入力端子です。
標準入力周波数は3.579MHzで、最大周波数は4MHzのようです。
15 D3
(AA0,TD8)
I
ポートピンです。

D0(mode) = Hのとき、D3(AA0)
内部レジスタのアドレス指定ピンとなります。

D0(mode) = Lのとき、D3(DD8)
トーンデータ一時ラッチレジスタピンとなります。
16 VCC - 電源端子です。
+5Vの電源を用意してください。





・タイミング図


「~CE」と「~WE」がともにLのとき、「D7」~「D0」の値がSN76489ANの内部レジスタにラッチされます。




・矩形波周波数設定レジスタ以外のレジスタへの書き込み

矩形波周波数設定レジスタ以外への書き込みは、D0(mode)ピンをHにします。
D3(AA0)(LSB)~D1(AA2)(MSB)で書き込み先レジスタのアドレスを指定、
D7(DD0)(LSB)~D4(DD3)(MSB)に書き込むデータをセットします。



・矩形波周波数設定レジスタへの書き込み

矩形波周波数設定レジスタへ書き込む場合、2段階書き込みが必要となります。
第一バイトの書き込みは「・矩形波周波数設定レジスタ以外のレジスタへの書き込み」の時と同様です。
第二バイトの書き込みはD0(mode)をLにします。
第二バイトが書き込まれた時点で、音程レジスタへデータ値10bitがセットされます。





・使い方

SN76489ANのレジスタは、おおまかに分けると4種類あります。

・矩形波周波数設定レジスタ(10bit)
・矩形波アッテネートレジスタ(4bit)
・ノイズコントロールレジスタ(3bit)
・ノイズアッテネートレジスタ(4bit)


矩形波周波数設定レジスタと矩形波アッテネートレジスタは、3チャンネル分で合計6個あります。

音を出すには、矩形波周波数設定レジスタと矩形波アッテネートレジスタを操作します。
矩形波周波数設定レジスタに任意の周波数設定値をセットし、矩形波アッテネートレジスタをミュート(0xF)以外にします。
キーオン・キーオフレジスタに相当するものは無いため、発音を止めるには矩形波アッテネートレジスタをミュート(0xF)にします。




ノイズ音の場合、ノイズコントロールレジスタとノイズアッテネートレジスタを操作します。
ノイズコントロールレジスタに任意の周波数・ノイズ源設定値をセットし、ノイズアッテネートレジスタをミュート(0xF)以外にします。
ノイズコントロールレジスタにセットできる周波数は4種類あります。
また、ノイズは2種類あります。

ノイズ音の場合もキーオン・キーオフレジスタに相当するものは無いため、発音を止めるにはノイズアッテネートレジスタをミュート(0xF)にします。





矩形波とノイズ音どちらのアッテネートレジスタも、値が大きいほど出力音が小さくなります。
よって、MIDIなどで受信したベロシティ値(Vmidi = 0~127)から、SN76489ANへ書き込み可能なアッテネート値(Vdcsg = 15~0)へと変換する場合には、

    Vdcsg = 15 - (Vmidi >> 3)

とします。


ハードウェアでのエンベロープ機能が無いため、時間的に出力音の音量変化を行いたい場合は、時間的にアッテネートレジスタを操作する必要があります。




・使用例

SN76489ANを3個使用して、矩形波最大同時発音数:9、ノイズ音最大同時発音数:3のMML演奏プログラム例をのせておきます。
(プログラム上では、矩形波最大同時発音数が8つでノイズ音はドラムに割り当てています。)

・回路図






デコーダ(74HC138)で複数のSN76489ANを接続できるようにしています。


上記回路例では、N76489ANの~WEピンは共通となっていますが、74HC138のみでチップセレクトとライトイネーブルが可能です。
ArduinoのD10ピン(~WE)を74HC138の4番または5番ピンに接続して、各SN76489ANの5番ピン(~WE)をグランドに接続する方法でも良いです。


回路上、SN76489ANは最大8個接続できます。


・Arduinoプログラム例
// SN76489ANでMML演奏プログラム
//©oy
// https://oykenkyu.blogspot.com/2022/01/SN76489.html

#include <TimerOne.h> //TimerOne.hをインクルード
#include "avr/io.h"
#include "avr/interrupt.h"
//このプログラム(スケッチ)ではTimerOneを使用しています。
//ですのでTimerOneをダウンロードしてインクルードしてください。

#define XTAL 16000000 //水晶振動子の周波数(MML演奏速度に影響)

#define SERIALSPEED 38400 // UARTのボーレート(デバッグ用)

#define CH 10         // SN76489AN最大チャンネル数
#define MML_MAX_TR CH // MMLトラック数

// SN76489AN_保存用
unsigned char sn76489_vel[CH]; //音量(4bit)

//デフォルトオクターブ[ch0,ch1,ch2,…,]
//増やすとオクターブが高くなります。(出力オクターブ = def_oct + (mmlのオクターブ))
char def_oct[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

// MML演奏用
// ATmega328だとdoubleでも精度はfloatと同様
volatile double mml_time = 0;      // MML演奏用時間カウンタ
volatile double mml_time_ms = 0;   // MML演奏用時間カウンタms
volatile char mml_time_cnt_en = 0; // MML演奏用時間カウント_有効無効(0で無効、1で有効)
volatile int tempo = 120;          //テンポ

//ドラム発音用
volatile int drumvel = 127;                                  //ドラムベロシティ(0~127)
volatile int intwr = 0;                                       //書き込み中か?
volatile int drum_set = 0;                                    //ドラム情報書き変え中か?
volatile unsigned char drumint[3] = {0, 0, 0};          //ドラムタイマー用,Hで発音
volatile unsigned long drumint_c[3] = {0, 0, 0};        //ドラムタイマーカウント用
volatile unsigned char drumint_t[3] = {0, 0, 0};        //ドラムタイマートグル検出
volatile int drumint_vel[3] = {0x7f, 0x7f, 0x7f}; //ノイズ音量
volatile unsigned int drumint_veldo_lef[3] = {0, 0, 0}; //ドラムタイマー音量分周

volatile unsigned char drum_freq[3] = {1, 1, 1};                 //ノイズ周波数
volatile char drum_veldo[3] = {0, 0, 0};                         //ノイズ音量減衰
volatile unsigned long drumint_cm[3] = {1000, 1000, 1000}; //ドラムタイマーカウント最大値
int recym_en = 0;                                                      //リバースシンバル発音中か?

unsigned char drum_note_num = 0; //音程セット保存用

//音程データテーブル
// 3.579Mhz用
unsigned int tone_data[128] = {
    855, 807, 762, 719, 679, 641, 605, 571, 539, 1017, 960, 906,
    855, 807, 762, 719, 679, 641, 605, 571, 539, 1017, 960, 906,
    855, 807, 762, 719, 679, 641, 605, 571, 539, 1017, 960, 906,
    855, 807, 762, 719, 679, 641, 605, 571, 539, 1017, 960, 906,
    855, 807, 762, 719, 679, 641, 605, 571, 539, 508, 480, 453,
    427, 404, 381, 359, 339, 320, 302, 285, 269, 254, 240, 226,
    214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
    107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57,
    53, 50, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28,
    27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14,
    13, 13, 12, 11, 11, 10, 9, 9};
// 4.000Mhz用
/*unsigned int tone_data[128] = {
  956, 902, 851, 804, 758, 716, 676, 638, 602, 568, 536, 1012,
  956, 902, 851, 804, 758, 716, 676, 638, 602, 568, 536, 1012,
  956, 902, 851, 804, 758, 716, 676, 638, 602, 568, 536, 1012,
  956, 902, 851, 804, 758, 716, 676, 638, 602, 568, 536, 1012,
  956, 902, 851, 804, 758, 716, 676, 638, 602, 568, 536, 506,
  478, 451, 426, 402, 379, 358, 338, 319, 301, 284, 268, 253,
  239, 225, 213, 201, 190, 179, 169, 159, 150, 142, 134, 127,
  119, 113, 106, 100, 95, 89, 84, 80, 75, 71, 67, 63,
  60, 56, 53, 50, 47, 45, 42, 40, 38, 36, 34, 32,
  30, 28, 27, 25, 24, 22, 21, 20, 19, 18, 17, 16,
  15, 14, 13, 13, 12, 11, 11, 10
};*/

//演奏のデータ
//簡易MML
unsigned int mml_data_len = 0xFFFF; //トラック終端「;」が入るため、適当な数値で良い

// mmlのトラック終端には必ず「;」を入れてください。
//使えるコマンド
//「;」             :トラック終端コマンド(トラック終端に必ず入れてください。)
//
//・全チャンネル(トラック)でパラメータを共有
//   「t」+ 数値:テンポを整数で指定
//
//・各チャンネル(トラック)でパラメータの独立指定が可能
//   「v」+ 数値      :ベロシティ(音量)を整数で指定
//   「p」+ 数値      :パン(音量)を整数で指定
//   「@」+ 数値      :音色を整数で指定
//   「o」+ 数値      :デフォルトオクターブを整数で指定
//   「l」+ 数値      :デフォルト音長を整数で指定
//   「<」または「>」  :オクターブを上げる、下げる(「<」で1オクターブ上げる)
//   「^」            :タイ(タイの次の音程コマンドでの発音は無視され、音長のみ取得します。音程の省略も可能です。)
//
//
//・音程コマンド
//   (音程)+(「+」または「-」)+(音長)+(「.」):音程はc,d,e,f,g,a,b,rで指定、+または-で半オクターブ移動、音長は整数で指定、ドットの数で(音調+(音調/2)+(音調/4)+(音調/8)+…)符点音長を追加指定
//   例:
//   「a4」    : 音階Aで4分音符
//   「c+16」  : 音階C#で16分音符
//   「g-1.」  : 音階G♭で符点1分音符(1分音符+2分音符の長さ)
//   「r.」    : 休符で符点デフォルト音長(デフォルト音長の初期値は4分音符です。変更したい場合は「l」コマンドで指定してください。)
//   「r2...」 : 休符で3符点2分音符(複符点2分音符)(2分音符+4分音符+8分音符+16分音符の長さ)
//
//・トラック10(ch9、0x09)のドラムパート場合のみ、和音(音長が0)が使えます。
//

//よいまちカンターレ
const unsigned char PROGMEM mml_data[] =
    "t180v80l16r1r1r8o7d8>a8<degf+ed>a8>a8r8<a8<d8>a8<gf+8.d8e8r8d8>a8<degf+ed>a8>a8r8<a8<d8>a8<gf+8.e8d8r8d8>b8<degf+ed>a8>a8r8<a8<d8>a8<gf+8.d8e8r8d8>b8<degf+ed>a8b8r8<d8e8f8>b<c+deff+gg+a4.r2r8>b8b8r2.b8b8r2r8>e4r8f+8r8g8r8g+8r8<<d8d8.c+64c32>b64r2..<abr2r8a8a8br2r8.d8g+8ar2r8.>e8<d8r2.g+8g+8r2..a8ar2r>b4a+8b8<c+4d+8e8f8d8d8.c+64c32>b64r2r8<e8d8dr2r8.d8rar2.e8d8<dr2r8.>>>>a4.r8g+2g4.r8b8.ra8b8a2<c8>a8<c8>a8b4<drdr8.>grb8r8a8<<e32r32e32r32a8r8>>a8g8d8<<a32r32e32r32>>f+8.<<e32r32a8r8>>f+4<a+8ra+32r32ar8.b8.rar8.f+rerb8e4.g8r8er8.<c+r8.eree>ererc+8r8>a+2r8<frf8.r>a+8r8<a+4a8.<dega4.g+32g32f+32f32e32r1r1r4...>>a4a8a8r8a8g+8a8b4b8br2r8.<<g+2..b4.r8<c4.>g+8<c+2.>g+8<c+8d1^d8>a2<c+2>b2<c2>e2..f+2g+4g+8g+8g+8e2..f+2a4.c+ef+g+b4.<c2>g+1^g+8a1g+1b4>b4<e4>b4c2d2r2.g+8.r>b8.b8rb8.rg+8b8b3r6g+8g+8<c4c+4.<<e4d+4>b4a1b1<c1^c1e1e4>b4<e4g+4r1r1r1r1r1r1r1r1r1r1r1r1r2..cr8.c8>g8<c8c32r32c32r32>b32r32b32r8r32d8rddc+64c64>b64a+64g+64f+64e64d+64r8.e4f+4r2<g+2..b4.r8<c4.>g+8<c+2.>g+8<c+8d1^d8>a2<c+2>b2<c2>e2..f+2g+4g+8g+8g+8e2..f+2a4.c+ef+g+b4.<c2>g+1^g+8a1g+1b4>b4<e4>b4<e4.r8f+4.r1r1r1r4.>e2.r8e8e8e4r8;v100l16r1r1r8o6d8>a8<degf+ed>ar>ar8.<a8<d8>a8<gf+8.d8er8.d8>a8<degf+ed>a8>a4<a8<d8>a8<gf+8re8d8r8d8>b8<degf+ed>a8>a8.r<a8<d8>a8<gf+8rd8e8r8d8>b8<degf+ed>a8b8.r<drerfr>b<c+deff+gg+ara8.^a64g+64g64f+64e64d+64d64c64>a+64a64r2r32a.r32a.r2.r32a.r32a.r4...r8.a4a8b8b8<c8c8c+8c+8r8>drd8>br<d8rdr8d8r8drdr>b32r32b<f+32r32f+re8rd8r8drdr>br<d8rd8.e8g8.rf+8.re8.rd4r8drd8>br<d8rdrd32r32d8r8drdr>br<d8rdr8drd4r2r8g4f+8g8a4b8<c8c+8r8>drd8>br<d8rdr8e8r8d32r32d32r32dr>br<f+8.e8rd8r8drdr>br<d8rd8.e8g8.rf+re8.rd4r4drd8>br<d8re8rf+8a8.r>br8.<d8rc+8rdrd4r4d8e8f+8g8a2r4d4a8.g8.f+4.g8.rf+8.rf+8.rb8.rf+8.re8..r32e8d2^d8r2d8.e8.f+ra8.rc+rc+rd8.c+6r48c+8d1r2e8rd8ra4r1r1r2.e8.re8er8.e8g+re8b8.rb8e8g+8.rf+re8f+re8.r<c+8.r>b8<c+8r8>g+8..r32g+rf+8e8f+8e8f+8g+8e8.r<c+8.r>b8<c+r>g+rg+8.rg+8f+8e8f+8e8g+8r8c+8.r4re8.r4rf+4r4g+4r4<c+8.r>b.r32b8g+.r32e8r8b8.r<c+8r8>g+8.rg+.r32f+.r32e.r32e8.r<c+8.r>b8<c+8r8>g+8.rg+rf+8e8f+8e8f+8g+.r32g+8.r8.<e8.rd+8.re2^e6r6r24>c+8d+8e8.rere4f+8.re8.re.r32e8>b8<e8.f+8.g+8b2..r4b8b8e8drerf+4e8r2r8b8.rb8.a8rg+8.rg+8<c+8>b3r6b8b8d+4e2^e6r6r24c+8d+8e4..r8.c+8c+8d+8e3r24e8.r>b8<e8f+8a8.g+8.e2..r4e8rer8b1^b1^b8r1r1r1e8r8<c+8.r>b8<c+8r8>g+8r8g+8r8erf+8e8f+8g+8e8.r<c+8.r>b8<c+8r8>g+8.rg+8f+8e8f+8.rg+8.rc+8.r4re8.r4rf+8.r4rg+8.r4r<c+8r8>brb8r8g+4b8.r<c+4>g+4g+rf+8.e1r1r1r1.re8.r<c+8.r>b8<c+8r8>g+4g+rf+8e8f+8e8f+8g+8e8.r<c+8.r>b8<c+r>g+rg+8.rg+8f+8e8f+8.rg+8.rc+8.r4re8.r4rf+4r4g+4r4<c+8.r>b8b8g+8e8r8b8.r<c+8r8>g+8.rg+8f+8e8e8.r<c+8.r>b8<c+8r8>g+8.rg+rf+8e8f+8e8f+8g+rg+8.r8.<e4d+8.re2^e6r6r24>c+8d+8e4ere4f+4e4e8e8>b8<e8.f+8.g+rb2..r4b8r8ererf+e32f32f+32e32d+32f32f+.r32e8.r1r1r1r>b2.r8b8b8b4r8;v100l16r1r1o3d8d8r4.>f+4a4r8a8a+4r8a+8b8b8b8r4.b8<e8d4d8r8>f+4f+8f+8g8g8g8g8g8g8g8g8f+8f+8f+8f+8f8f8f8f8f8e8e8e8f+8f+8f+8f+8g+8g+8g+8g+8a+8a+8a+8a+8f+8a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8a8a8b8b8<c8c8c+8c+8d8d8r4d8rd8r8.>g+8g+8r4g+8rg+8r8.g8g8r4g8rg8r8.g8g8g8g8d8<d8>d+8<d+8>e8e8>e8r8<e8re8>e8r<a8a8>a8r<a8.ra8.d+8d8d8c8>a8<d8c8d8g4f+8r8a4<f+8r4d8d8>d8d8d8d8d8d8<d8d8>d8d8d8d8d8d8<d8d8>d8d8d8d8d8d8<d8d8d8d8a8d8f+4>b4b8b8a+4a+8a+8a4a8a8a8.a8.a8d4d8<d8d8d8>a8g+8g4g8g8<d8>g8<d8>g8g4d8d8d8g8<d8c+8>f+4f+8f+8a+4a+8a+8b4b8b8f+8f+8g+8a+8e4e8e8b8e8b8e8f+4f+8f+8<c+8>f+8<c+8>g8g4g8g8g8g8g8g8g4g8g8g8g8g8a8a8a8g+8g<g8r>f+8g8g+8a8a8g+8g<g8r>f+8g8g+8a8a8g+8g<g8r>f+8e8a8a8a8a8r8a8g+8a8b4b8b8r2r8e8<e8>e8<e8>e8<e8>g8b4b8b8<c4c8>g+8<c8c+8c+8c+8c+8c+8>g+8g8e8e8<e8>e8<e8>e8<e8>e8f8f+4f+8g+8a8a8a8a+8b4b8b8<c4>b8<c8c+8r8c+8c4c8c8>b4b8r8a+4a+8<d8>a+8a8a8a8a8a8a8a+8b4b8b8a4a8a8a8g+8g+8g+8<c4c8c8c+4c+8c+8>e4<e8>e8<e8>f+4f+8f+8f+4f+8f+8g+4g+8g+8g+4g+8g+8a+8a+8a+8a+8a+8a+8a+8a+8<c4c4d4d8c+8>a+8a+8a+8a+8a+8a+8a+8a+8a4a8a8a8a8a8a8g+8<g+8>g+8g+8<c8c8c4c+8c+8c+8e4c+8>g+8g8f+8f+8f+8f+8f+8f+8f+8g8g+8g+8g+8g+8g+8g+8g+4a8a8a8a8a8<a8>a8<a8>a8<a8>a8<a8>a8<<e8>>a8a+8b4<b8>b8b8b8b8b8b8b8b8b8b8b8b8b8<<d2>b2<c2>a2r1r1r1r1r1r1r1r1r1r1r4.>c8<c32c+64d64d+64e64f+64g32.f+64f64e64d+64d32c+32c32>b<c8c8>g8<c8g8c8>b8<c8d4d8r4c4d4c8>f+g+a+b<c+e>e8<e8>e8<e8>e8<e8>g8b4b8b8<c4c8>g+8<c8c+8c+8c+8c+8c+8>g+8g8e8e8<e8>e8<e8>e8<e8>e8f8f+4f+8g+8a8a8a8a+8b4b8b8<c4>b8<c8c+8r8c+8c4c8c8>b4b8b8a+4a+8<d8>a+8a8a8a8a8a8a8a+8b4b8b8a4a8a8a8g+8g+8g+8<c4c8c8c+4c+8c+8>e4<e8>e8<e8>f+4f+8f+8f+4f+8f+8g+8g+8g+8g+8g+8g+8g+8g+8a+8a+8a+8a+8a+8a+8a+8a+8<c2d2>a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8r4e2.r8e8e8e4.;v80l16r1r1r8o4a8r2r8a4.r8<a+3r24a+8b8r1c4>a8<c4<<<c64>b64a64g32f64e64d64c64>b64a64g64f32e64d64c64>b32a32g32.r2..a2g+2r8g4r8f+4.d8f+4.r8a+2^a+6r1r1r1r1r1r1r1r1r1r1r2....r4.g+24a48a+48b32<c48c+48d48>a96a+96<c96c+96d96d+96e96f+96g96g+96a96a+96b96<c96c+96>d+96e96f+96g96g+96a96a+96b96<c96c+96d48d+96e96f96f+96g96g+96r96a96a+96b96<c96c+96d96d+96f96r1r1r1..>>f+4<d4>a8.r<d8.r>g+8.rg4r1r8ar<d4c8.r>f+rd8f+8<d8r2r8>a8f+4<c+8c+8>b8.rc+4a8e8c+8r8f+8.rb8b8f+4<drd8c+8r4>e8g8b8<d8>e8r4.f+8a8<c+8r8>f+8c+8r8<f8r4>>a+8<<e8.r4r>f8.ra+8>a+8<a8.rg8a8r1r1r2..>>e4e8e8r8e8d+8e8f+4f+8f+8r2r8<e8>b8<e8e8>b4<e8>b4b4<c4.c4e4g+4.g+4>b4r8<g+8r8>b8<e8>b8g+8<c+4.r8>f+4.r8f+4.r8<g+4<g+r8.<g+8e8>b8<g+8r8e8>b8<f+8r8>a8f+8<d.r64<a64g64f64e64d64c64>b64a64g32f64e64d64c64>b64a32g64f64e64d32c32>a+64a32g32ee4c+8e4c+8e8f+4f+4f+4.d+8r8b4g+8<c4.r8c+4.e8>g+4e8e8e8f+4f+8f+8f+4f+8f+8e8e8e8e8e8e8e8e8<<b8e8>b8<e8e8.f+8.a8<c8>>e4.f+2>e4.g+4.g+4b4.b4.b4d+4r4g+4.r8c+4.g+4.r4>f+4.f+4.f+4g+4g+8g+4.g+4<<<c2f+2e2c2>>>f+4.f+4.f+4f+4.b2^b8<<<e8c8>g8.re8b8<f+8.r>>c2>a2r4<<<c+8>g+4f+8e8f+8>>e2..b2<c2^c8>c+2..e2b4e4.f+2a2b2<c2>c+4.c2>a+2b2..r1.r8<<c4c8c8r8<<c8>b8<c8d8.rd8r4>>>g4a4r<<ef+g+ab<c+d+>>e8>b8<e8e8>b4<e8>b4b4<c4.c4e4g+4.g+4>b4r8<g+8r8>b8<e8>b8g+8<c+4.r8>f+4.r8f+4.r8<g+4<g+r8.<g+8e8>b8<g+8r8e8>b8<f+8r8>a8f+8<d.r64<a64g64f64e64d64c64>b64a64g32f64e64d64c64>b64a32g64f64e64d32c32>a+64a32g32ee4c+8e4c+8e8f+4f+4f+4.d+8r8b4g+8<c4.r8c+4.e8>g+4e8e8e8f+4f+8f+8f+4f+8f+8e8e8e8e8e8e8e8e8<<b8e8>b8<e8e8.f+8.a8>>g8r4.f+3r1r1r1r4r6b2.r8b8b8b4r8;v80l16r1r1o7a24f+12df+>ardrdf+a<d<d>ae>ae<ec+>beaa+<c+>a+<c+g+f+48g+24>>a+<de>a+<<f+d>a24<d24f+24>>ar<degra+r<d>agf+a<ec>a>a<deg<e24a24e24>f+rgrdrbgb24<f+12bf+d>b<d>aba<f+d>br<eec+48>a24<e>c+24e24a24<c24d24e24>>g+8<ce<<d>ad>af+ed>bf+b<deec+>af+<<<dc>ba>>g+8<<c24d24f24bfcf>af<af>af+>a+<f+g+rg+24a24r2.r24g+r2...g+r1rc+r1r1r1r1r1r1r2...r4.>a32r32a32r32a32r32a32r32a32r1r4...<d32r32e32r32g32r32a32r3r96e32r32a32r4...f32r.a32r4r32f32r32a32r3r96b32r.f+32r32a32r4r32c32r32d32r32a32r1r8..f+32r.e32r3r96a32r.g+32r.f+32r1r.d>f+r8d24f+24a24<d24a24<c+24d24r12f+24r12e24f+24r24f+24g24a24f+24a24b24<c+24>b24r24<c+24>a24f+24drbaf+r8.ab>c+4f+8<e8e>ea<c+>e>ar8<e>>c+8.<<arer>a+8f+a+<f+>f+<c+8>a8>b<df+ed>b<af+>b<f+<c+>baf+<<e>>g<e24r12e32g32b32r32f+32b32<d32r32d24r48>b24r48g24r48>>bb<ge>b<<c+24e24g+24f+24c+r48>c+ec+>f+c+8<f+>f+<c+24f+24<f+24a24f+24>b24>gda+a<d8>g8d<d>a+ga+gd8a+g<dgg8>g<g>d8dg8ar8r32<e32<eer1r1r1r1r2.r>g+rbr<erer>>br<g+rerg+rg+r<g+rd+r>g+r<d+d+g+rg+rd+d+>g+r<g+g+>g+ec+>g+r8<g+r8eg+r>br<erg+r>br8.g+<e>bg+br4rf+r<er8<<c+32f+32ac+>>>f+rar<f+b<f+b>f+>b<erg+r>g+r<g+>b<erg+r>g+r<cr>g+<cr>g+<crcr>ar<<ef+af+>>br<fr>fa+<fa+<dc>a+eg+ec+g+ec+>aer32<<e32g+32e32g+32r.>e>bf+r8.bf+<ebf+a<c+>baf+ec+>af+<g+f+32b32<d+r>crcrcg+32<d+32f+32r32f+32r32>>g+r<er<<g+d+c+>bg+ec+>bag+ec+>b<c+ec+>f+<c+f+a<c+f+a<c+e>ac+>af+c+>ag+<eeg+b<eg+e<e>bg+>beg+e>bg+<a+rg+rerg+g+<<e>bec+>bg+ec<grecgrcra>f+d>a<<arerdr>eg+<er>g+r<c+>g+<g+e>g+r<c+r>br<f+rer>br<erf+d+>br<br>d+rg+rd+rbr<cr>>g+r<crd+rerc+rc+rg+r8.c+rererer>f+r<<ec+>af+ec+>f+c+>a<c+f+a<brd+r>g+r<d+rg+rd+>g+d+rg+r<ar>ar<cr>erer<cr>erar<er>>ar<crercrer4r>br<f+rbr<erf+r<cr>f+r>brf+b<f+a<cec>a>b<f32r32f+32r32g32r32g+32r32a32r32a+32r32b32r32<e2<e2e2f+2r4<c+8>g+4f+8e8f+8>b2er>>e4<b4>b4<<c8>g+8c8<d+r8.c+4.r4>>c+4<d4>e4r8<<dr4..>f+4<f+r8.c+4>>a4<b4<br8.c4>d+4<g+4>>c+8<<g+4>>c4<<g+4>>>a+4<<<c2^c8<e6c6>a6e6c6>a6e4c4r1..<<ed+c+>bgec+>bagec+r2g+rbr<erer>>br<g+rerg+rg+r<g+rd+r>g+r<d+d+g+rg+rd+d+>g+r<g+g+>g+ec+>g+r8<g+r8eg+r>br<erg+r>br8.g+<e>bg+br4rf+r<er8<<c+32f+32ac+>>>f+rar<f+b<f+b>f+>b<erg+r>g+r<g+>b<erg+r>g+r<cr>g+<cr>g+<crcr>ar<<ef+af+>>br<fr>fa+<fa+<dc>a+eg+ec+g+ec+>aer32<<e32g+32e32g+32r.>e>bf+r8.bf+<ebf+a<c+>baf+ec+>af+<g+f+32b32<d+r>crcrcg+32<d+32f+32r32f+32r32>>g+r<e<<<c+>g+d+c+>bg+ec+>bag+ec+>b<c+ec+>f+<c+f+a<c+f+a<c+e>af+>af+c+>ag+<eeg+b<eg+e<e>bg+>beg+e>bg+<a+rg+rerg+g+<<e>bec+>bg+ec<gdrcgrcra>f+d>a<<arerdr1....ebag+ebag+e>b<ed+e>b<d+er4e>b<er>b<eg+e>b<eg+e>br<b>b<ef+g+>b<er8.;v80l16r1r1o4d4r4.>f+4<e4.r8f+2r8>b4r4.b8<e8d4.r8f+2^f+8g2..c+2c2r8e4f+4f+4g+2^g+8a+2^a+8>a8a8r2.a8a8r2.a8a8r2r8a4a8b8b8<c8c8c+8c+8d8d8r2.>g+8g+8r2.g8g8r2.g8g8r2.e8e8r2.a8a8r2.d8d8r4d8ddddg4f+8g8a4b8<c8c+8d8d8r2.>a+8a+8r2.<d8d8r2.d8d8r2.>b4.r8a+4.r8a4.r8a8.a8.a8<d2r2>g1^g4d2r4f+2a+2b1e1f+1<<<fdfa+<dr>>a+<<d>e>a+<d>a+<ede>a+<fdfa+<dr>>a+<<d>f>a<d>a>>g8a8e8<e8g+>f+>a<<<g>>>g8a<gg8g+8e8<e8g+>f+>a<<<g>>>g8a<gg8g+8e8<e8g+>f+>a<<<g>>>g8a<ee8<<<e8.re8ere24f48f+e8d+8e8f+8.rf+8f+8r2r8>>>e2.g8b4.r8<c4.>g+8<c8c+2^c+8>g+8g8e8e1f+4.r8a4.r8b2<c2c+4.c2>b4.r8a+4.<c+8>a+8a2.a+8b2a2^a8g+4.<c2c+2e2^e8>f+1g+1c+2..r8<c2d2<e1>b1g+2<c2e4.>e2^e8c+1d+1e1^e1b1^b1r1r1r1r1r1r1r1r1r1r1r1r1r2..c8r8c8c8r8c8>b8<c8d4d8r4c4d4r2>e2.g8b4.r8<c4.>g+8<c8c+2^c+8>g+8g8e8e1f+4.r8a4.r8b2<c2c+4.c2>b4.r8a+4.<c+8>a+8a2.a+8b2a2^a8g+4.<c2c+2e2^e8>f+1g+1c+2..r8<c2d2e2r1r1r8>c+8d8d+8r4e2.r8e8e8e8r4;v90l16r1r1r2r8o3f+4a4r4a+4r8a+8b8r2.b8d4.r8f+2r8g2..f+2f2^f8e4r8f+4.r8g+4.r8a+4.r4<e.g32e8>>g+f+g<c8r>e8f8f+8<<e8e8b>>fg<c8r>e8f8f+8<<e8e8>>g+f+g<c8r>e8f8<a4.b4<c4c+4f+r8.c8r8dr8.dr8.c8c8r4g+r8g+8r8.>b8b8e8eeg8ggr8e8a+8a+8e8e8<er8e8r8.e8g8>>b8bb<e8re8e8r<e8e8er8.er8e8r8.d8d8dr8.d8d8dr>g4f+8g8a4b8<c8c+8d8>d8d4d8d<d>dd<c+dr8drd>dfa<dr>d<d>dd<c+d>d8d8d8c+dd8d<d>dd<c+d>>a8g8<ard8ard8f+rd8b8<f+rbrf+r>a+8<drg+r>a+r>a8<ar>a8<ar>a2<a2<d8>a8<c8>a8r4<d8.dr4d4r4>a8a4.r4<c+4.r8f+8r8f+4f+4f+8.f+r4f+4r4e8e4e8r4c+4c+8c+4f+8c+8r8>g1..r4ar<er>f+regr<e>f+rgrg+rar<er>f+regr<e>f+rgrg+r8e<er>f+regr<e>f+rgra4a8a8r8a8<d+8>a8b8.rb8b8r2r8<e4<e4.>b4<g+4.r8g+2..g+3r24g+8.rg+4.r2.>c+4f+4a4<e8r8>b4<f+8e8>g+4r4g+4r8g+4r4f+2>a+4.r4<e2..b4r8b8a4.r4g+4g+8<c4r4c+4.r8>>e4.r4<f+1g+1a+2..r8>c4..rd4..ra+8a+8a+8a+8a+8a+8a+8a+8a8a8a8a8a8a8a8a8g+8g+8g+8g+8<c8c8c8c8c+8c+8c+8>e4<c+8>g+8g8f+4f+8f+f+f+8f+8f+8ggg+4g+8g+g+g+8g+8r8g+8a8a8a8a8a8a8a8a8a8a8a8a8a8e8a8a+8b4b8b8b4b4b1r1r1r1r1r1r1r1r1r1r1r1r1r2..<<g8r8f8g8<c8c8>b8r8d4r4.>c8.rd8.r2re4<e4.>b4<g+4.r8g+2..g+3r24g+8.rg+4.r2.>c+4f+4a4<e8r8>b4<f+8e8>g+4r4g+4r8g+4r4f+2>a+4.r4<e2..b4r8b8a4.r4g+4g+8<c4r4c+4.r8>>e4.r4<f+1g+1a+2..r1r4.>>g+f+g<c8r>e8f8f+8r4rfg<c8r>e8f8f+8r4g+f+g<c8r2r8.e2d+dc+32c32>br8<e8e8d+32d32c+32c32>b32a+32ar8;v90l16r1r1o5d4r4.>d4c+4.r8a+4.a+4<f+4r4.>f+4f+4.r8a2^a8b2..f+2f2^f8b2.r8<d2>f2^f8<c+4r2.c+4r2.c+4r2r8>a4a8b8b8<c8c8c+8c+8r1r1r1r1r1r1r2..>g4f+8g8a4b8<c8c+8>>>d1^d1^d1..r4<<<f+2f2e2d2>f+1<f+1e4.e4.r4e4.r8>a+2<a4f+2^f+6r12d4.d2r8e4.e4.r4a+4.f2^f8g2f4g8c+8e4r1r1r2r8<b8.rb8br8.arg+rarb8r8b8b8r2..g+8g+g+r8g+8r8g+8r8g+8r8g+8r8g+8r8g+g+e8rg+g+8eg+r8g+8r8g+8r8g+8r8g+8r8g+8r4>a8<f+8c+8f+8c+8a8f+8a8f+8b8f+8e8g+8<c8>g+8<c8r8>e8r4.e8r8f+8r8a8r8<d8r2.>e8eer8g+8r8g+8r8g+8r8f+8r8f+8r8f+8b4r8g+8r8f+8r8g+8r8g+8e8g+8r8g+8e8g+8r8a8r8a8r8a8r8a8r8g+8r8e8r8b8r8b8r8e8r8e8r8e8r8e8>g4.r8f+2<g+4e8e8r8g+8r8e8r8d+8r8d+8r8f+8>b8<f+8>b8rbbrbb<c8ccr8ccr8g+8e8g+8r8g+8e8r8c+re8c+8eer8e8r8eer8f+8d+8f+8r8f+8r8d+d+e8cc>a8<cce8c8r8>aa<e8c8>a8<ccr8c8r8>a8<e8e8r8e8r8e8r8e8r8e8e8eer8e8e4>g4.r8a4.r8a+4.r8b2r1g+2..g+2g+2^g+8e2..g+2d4.>b4<a2f+2<d+2>g+2e4.e2e2e2^e6r1.r3a+4a+8a+8r8<c8>b8<c8>g4g8r4g4a4ref+g+ab<c+d+r4g+8g+g+r8g+8r8g+8r8g+8r8g+8r8g+8r8g+g+e8rg+g+8eg+r8g+8r8g+8r8g+8r8g+8r8g+8r4>a8<f+8c+8f+8c+8a8f+8a8f+8b8f+8e8g+8<c8>g+8<c8r8>e8r4.e8r8f+8r8a8r8<d8r2.>e8eer8g+8r8g+8r8g+8r8f+8r8f+8r8f+8b4r8g+8r8f+8r8g+8r8g+8e8g+8r8g+8e8g+8r8a8r8a8r8a8r8a8r8g+8r8e8r8b8r8b8r8e8r8e8r8e8r8e8r1r1r1.r8>>>g+8a8a+8r4<e2.r8e8e8e8r4;;v110l16r2...o3a+g0<c+0>>g+0<cd0>gg0<a+0dd0>g<c0d0a+0>g<d0>g<ddc0<c+0>a+0g>g0<dd0a0>g<d0<dc0o10g0ro4c>ba<f0a0e0>c0<c+0>g+ra+0<e0>drg+0cra+0drc0g+dd0a+dd0g+0c0<are0c+0>a+0drc0g+ra+rc0<<d0>>g+r<a0>a+0drg+0ca0d<<c0>>d0a+d0<c>b0g+0cba+0gr<f0>g+0c0<c+r>a+0<c+0>d0<fr>g+0crd0a+rg+0cdd0a+dd0g+0<a0>crd0<c+0>a+0<er>g+0cr>d0<a+0<e0ar>c0g+rd0a+0<ar>g+0cra+0drd0c0g+rd0a+r<a0>g+0<e0f0>c0<c+r>a+rg+0c0dra+rg+0cra+rg+0c0dra+rg+0cr<d+0>a+rc0g+0dra+0<c+0d+r>g+0cda+0<d+r>c0g+0dra+0d0<d+r>g+0cr<d+0>a+rd0g+0cra+0<d+r>c0g+ra+0<d+r>c0d0g+r<c+0d+0>a+rg+0cr<d+0>a+rg+0<c+0>c0dra+0<d+0ar>e0g+0c0<d>d0<cd0>a+0ea0de0c0b0<c+0>g+d0ba+0b0ed>g0<a0crd0>g0<agc0agffa0c0>>ar<<g0c+ra0cgaaa0>g0<cra0d0>g<gc0agffc0argra0cgaac0>g0<ard0>g0<agc0agffa0c0o10g0o1ar<<c+0gra0cga+0b0>g0<erd0cra+0>gr<c0a+0>g0<dra+0>grg0<c0a+0dr>g0<a+rc0d0>g0o10g0o3a+r>g0<a+r<c+0a0>cr>b0<frf+0c0gg>c+0br<g0crf0>b0c+r<f+0g0crf0>b0c+r<crf+0>c+0br<g0f+0cg>c+0br<c0grf0>c+0br<f+0g0crf0>b0c+r<crf+0>br<g0f+0cg>c+0br<g0cr>c+0<f0>br<c0a0f+r>b0<f0>c+r<c0fr>b0<fra0cr>b0<grf0cr>b0<grc0f+0gr>b0<f0>c+r<c0fr>b0<frg0f+0cg>c+0br<c0gr>b0<frf+0c0gr>c+0<f0>br<f0cr>br<f0c0f+r>c+0<f0>b<fa0crf0>b0c+r<f+0c0ar>b0<frf0cr>c+0<f0>br<c0ff>b0c+0<grf0cr>b0<frf0cfd0<c+0>>b0<arcrg+0>br<cr<a0>a+0<d0>d0>br<<d0>cr>b0<<c+0>b0o10g0o3dr>>e0<<d0a+d0a+0>>ee0<<a+0>b0<d>>e0<<a+0d<a0>c0<c+r>brc0a0f+rgrc0>c+0<frfra+0d0c0frfrc0frbrc0f0f+rfr>c+0<c0frfrc0a+0d0frfrc0frbrc0f+0frfrc0>c+0<frfrd0c0a+0frfrc0frarf+0c0frfr>c+0<c0frfrc0d0a+0grarc0<c+r>f+f+crf+f+crf+f+c0d0a+r8.crf+ro10g0o2d0<crf+rc0d0ar8d0gr8a0drcr8.crd0a+ra0crgro10g0o2d0<a0cr8.cr8.c0>g0<d0a+0o6ar8o3f+0>g<c0f+r8.>g0o6a0o3d0a+0o6b0o3cr8.crf+f+a+0o6a0o3d0>g0<crf+>g0<f+cd0>gr8<d0o6a0b0o3a+0c0>gr8.c+0<c0f+>c+<f+0>c+c+<c0o6a0o3a+0>g0<<d0>dr>c+0<f+>g0<f+0>c+<c0f+rf+ra+0c0d0>g0o6a0bro3f+rcr>c+0<f+f+0>c+<c0o6a0o2f0<a+0drro6g+0o2c+32^32<g+0>g0<cdo6g+32r.o3c0>g0o6b0o3a+0d0o6aro2g0<drcrf+0>c+r<c0>f0o6a0o2c+0<a+0drrf+0>c+0g<f+0crf+rc0o6a0b0o3d0a+>f<f+f+cr>c+0<f+f+0>c+g0<a+0d0o6a0o3crf+f+0>g<cd0>g<f+r>g0<a+0o6b0a0o3c0drf+rc0f+f+f+f+c0d0o6a0o3a+f+f+f+f+0cf+f+f+d0o6b0a0o3c0a+f+f+f+c0f+f+f+f+c0a+0df+f+f+c0f+0o10g0ro3f+f+f+f+0cra+0dra0<a0>c0<c+r>a<d+>g0c0g+0>>ar<<frg0cra+0frg0>>a0<<c0a+0draro6c+0o3f0a0>g+0<cr>g+0o6c+0o3f0arc0g0g+0>>ar<<frc0grf0a+rd0a+0c0g0>>ar<<ard0a0cra0>gg0<a>g0<g+0>>a0<<g0d0c>g0<gg0>g<f0>g<a0o10g0o3c0>g0<d0>d0r<a0>gg0<b>g0<aa+0d0>>a0<g0<c0gga0d0<c+0a0>a+rcrd0a0a+0<c+0ara0>a0d0c0a+0<c+r>f+r<c+0>a0a+0d0c0<arc+0>a+0a0d0<ar>d0a0<c+0>c0a+0<aro10g0o3b0a+0<a0c+0>dr>d0r8<c0brd0a+0<a0c+r2r8.>c0<a0c+r>a+r<<c0>>c0g+0>a+0<dra+dc0g+ra+rd0c0>a+0<g+ra+rc0g+r<e0>a+0<d+r>c0d0>a+0<g+0<<e0>ere0>a+r<<c0>>g+0cd0<<d>>a+0<e0>d+rc0g+0d0>a+0<d+r<d+0>a+rc0g+r<d+0>a+r>a+0<c0d0g+ra+0<d+>dg+0cra+rd0g+0>a+0<cra+rg+0cr<e0>a+0<d+r>c0>a+0<g+0d0<<d+r>d+0e0>a+rc0g+0<<c>>d0<<e>>a+0d+0<er<e0>>d0d+0c0g+d0<<cd0>>d0a+ra+0c0<ar8.>c0a+0d0>a+0<<c+rd+0>f+0<c+>da+0<a0>crf+0<d+r>c0<a0>f+0>a+0<drf+0<d+r>a+0cr<d+0>f+0<fr>d0>a+0<<c+0<c0>>crf+0<c+0d+<d0>>d<<c0>e0a0>c<<d0>>da+0<d+r>>a+0<d0g+0c0<<dr8>>d0a+g+0cra+0<e0d+r>>a+0<d0<<c0>>c0g+ra+0<c+r>c0g+ra+0<c+0d+r>g+0>a+0<c0dra+rg+0cr<d+0>a+r<<d+0o2a+0<c0d0g+r<c+0>a+rc0a+0o10g0o5c0>>d<<d0>>a+0dd0<c+0>a+0>d0o5cc0>>a+0dc0a+0<c+0<c0>>d<<c0>>a+0da+0<<c0>c+0>dd0a+0<<d>a0c+0>cra+rc0>a+0o5d+0>>d0g+r<d+0>a+<<d0>>dc0g+0<<cr>d+0>a+r>a+0<c0d0g+ra+0<d+r>g+0cr<d+0>a+r>a+0<d0c0g+ra+0<d+r>g+0cd<d+0>a+0d+rc0d0>a+0<d+0g+ra+0<d+r>g+0cr<d+0>a+rg+0c0d0>a+r<a+0<d+>dg+0cr<d+0>a+rd0g+0c0>a+r<<d+0>a+rc0g+r<d+0>>d0<a+0<c+r>d0g+0c0>a+0<<c+rd+0>a+rc0g+<<e0>>da+0<d+0a0>>d0rro5d0>>d0g+0>a+0<<a0>cd0<<d>>d0a+0<<c0>d+0ar>c0g+r<d+0>a+rg+0c0d0>a+r<a+0<d+>dg+0cr<d+0>a+r>a+0<d0c0g+ra+0<d+r>c0g+r<d+0>a+r<<d0>>d0o6g+0o3c0>a+0<g+r<d+0>a+rg+0c0o6g+0>ce0>>da+0<d+r>c0g+0d0<<c0o2a+r<<d+0>a+rg+0>d0<b0d0c0<<c>>d0b<d+0>b0a+0d0cd0bc0d0>a+0<b0g+b0d<d+0>d0c0a+0bb0dc0b0<<c0>>g+0db0da+0d0b0c0<d+>d0b>a+0<g+0b0c0db0dd0<d+0>c0b0a+b0d<arc+0>a+0dr>g+0<<c0>c>b0g+0<ba0>g+rg+0<b0<c+rg0>a0d0a+rc0b0>g+<a0>g+0bg+0<gfa+0<e0a0>c0<c+r>g+a+>a+0<c0d0a+rg+a+0dc0g+ra+rg+0d0c0>a+r<a+rc0a+rg+a+a+0d0>a+0<crg+a+c0g+da+r<e0>g+0c0d0>a+r<a+rc0a+rg+a+a+0>a+0<c0dra+f+0a+0dc0g+ra+rg+0>a+0o5d+0>>c0dra+rcrg+rc0>a+0<a+0drg+a+cda+r>a+0<a+0d0cr8>g0<g+c0<a0c+>f+a+r>a+0<g+0c0dr<d+0<<g+0o3a+a+c0g+ra+r>a+0<c0g+0dra+a+c0g+ra+r>a+0<c0d0a+a+a+a+g+0cda+rc0g+0>a+0<dra+a+<a0>f+0cr<a0>a+r>a+0<c0d0g+a+a+rg+0cf+a+dc0>a+0<g+ra+a+c0f+ra+r>a+0<g+0d0cra+a+g+0cf+0>g0<da+rd0>g0a+0<g+0cra+a+0df+0c0<c+r>a+0<f0d+r<f+0>>g+0c0d0>a+0gr<a+0<f0d+>a+g+0c0<b>f+a+0<d+0fr>g+0>a+0<d0c0>g0o5f+r>d+0f0>a+rc0a+0<a0>f+0<e0c+0>d0>f0r<a+0da+0dd0a+c0<d+0>d0a+a+0da+0da+0dd0c0g+>>g+g+0<<a+>>g+o4d+0o1g+0<<c0g+>>g+g+0<<a+>>g+<<c0>a0<<c+0ar4..f0>>a0<cr4..>a0<c0<d+r4..>c0<c+0g0>>ar1^1^2...o6b0>>f+r4..<<g+r1^4..b0>>f+r4..<<g+r4..g+r4..g+r4..>>f+0<<b0g+r1^1^1^1^6...o3d0>g0<<c+0>a+rcra+0d0>f0grg0<a+0d0crg+r>g0<a+0c0da+b0a+rc0ara+0g0<a0gr8.>c0g+ra+0d+0<c+0g0>d0>g0<<ar2r8.>a+0d0>g0<<c+>>g0<d0a+>g0<a+0d>g0<d0a+a+0d0>g0<<c+>>g0<a+0dd0<c+0>>g0<a+a+0>g0<d<c+0>c0<ar>a+rc0g+0>a+0<d0<<cr>>a+dg+0cra+r>a+0<c0d0g+ra+rg+0cr<e0>a+0<d+re0>g+0<<e0>>c0d0>a+r<a+0<er>g+0c0<<c>>d0<<d>>a+0<e0>d+rg+0>a+0<d+0d0cr<d+0>a+rc0g+r<d+0>a+rg+0>a+0<c0dr<d+0>a+dg+0cra+rd0c0>a+0<g+ra+rc0g+r<d+0>a+0<er>g+0d0c0<<d+0o2a+r<a+0<d+0er<c0>>g+0cd0<<e>>d+0a+0<er>c0d+0d0g+0<<e>>d0<<cd0>>dd<a0>c0a+r8.d0a+0<c+0>c0>a+r<<c+0d+0>f+d<a0>c0g+ra+0<d+r>>a+0<f+0<a0>c0dr<d+0>a+rg+0cra+0<d+0fr>>a+0<<c+0>f+0<<c0>>c0dr<c+0d+0>a+<<d0>>d<a0<c0>e0>c0g+d0<<d>>a+0<d+r>c0>a+0<d0<<d0>>g+r8d0a+g+0cra+0<e0d+r>g+0c0<<c0>>d0>a+r<<c+0>a+a+g+0cra+0<d+0c+r>d0>a+0<g+0cr<a0>a+rc0g+ra+0<d+r>c0>a+0<g+0drd0<c+0>a+r<<c0>>g+0o10g0o3cd0<d0<d0>>b<c0<c0>>f+0a0<c+0>>d0ro5c0>>a0<c>d0<<c0>>a+0g0c0<a0c<c0>c0>g<c+0>a+rc0<c+0ar>a+rg+0c0d0<<d+0o2a+r<<d+0>a+d0<<d>>g+0<<c0>>cr<d+0>a+r>a+0<g+0c0dra+0<d+r>c0g+r<d+0>a+r>a+0<g+0d0cra+0<d+r>g+0cd<d+0>d+0a+rd+0c0g+0>a+0<dr<d+0>a+rc0g+r<d+0>a+r>a+0<c0g+0dra+0<d+>dg+0cr<d+0>a+rg+0d0c0>a+r<<d+0>a+rc0g+r>d0<<d+0c+0>a+rg+0c0d0>a+0<<c+r>a+0<d+r>g+0cd0<<e>d+0a0>a+0>d0rr<c0<a0<d0o2a+0<g+0d<<d0>>da+0<<c0>d+0a0>dr<a0>g+0<c+0>cra+0<d+r>>a+0<g+0d0cr<d+0>a+d<b0>c0g+ra+0<d+r>>a+0<d0c0g+r<d+0>a+rg+0<c+0a0>cr<d+0>a+rc0d0>a+0o6g+0o3g+0<<dr>>a+0<d+r<<g+0o3g+0<<c0>>c0<b<e0>>da+0<d+r>d0g+0<<c0o2a+0<cra+0<d+ra0>g+0>d0<d0<<c0>c+0b0>b0c0>gg0<d0ba+0<d+0>>g0<d0b>g0<b0db0>g0<c0g+0>a+0<db0>g0<d>g0<d0<d+0>a+0bb0>g0<db0<<c0>>c0g+0>g0<dd0>g0<bd0a+0<d+0>>g0<bb0>g0<dg+0d0b0c0>a+0g<d0>g0<bd0>g0<<d+0>a+0bd0b0>g<c0>c+0<<c+0>a+r8>c+d+rc+r<c0a+0>c+0<<ar8>>c+o10g4o4a0>a0<c+0>cra<d+>f0c0g+0brggc0argrc0g+0ffara0f0c0o6c+ro3f0a0o6c+ro3b0g+0c0frggc0argrg+0c0ffara0c0frf0arb0g+0c0frgga0o10g0o3cgffg+0c0aagf<a0>>d0<<c+r8.>c0g+0dr8.c0bbbba0cagfcrd0<c+0>a+0<a0o1ero4c+0a0g0>a+0dra+0>>e0o4c+0>d0<a;";


/////////////////////////////関数

//SN76489ANへ書き込み(第一バイト)
inline void sn76489an_write_1(unsigned char cs, unsigned char adr, unsigned char data);
// SN76489ANへ書き込み(第二バイト)
inline void sn76489an_write_2(unsigned char cs, unsigned char data);

//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum);
//メインの音量とパンのセット
void main_vel_pan_set(unsigned char vel, unsigned char pan);
//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel, unsigned char pan);
//チャンネルノートオン
void note_on(unsigned char ch);
//チャンネルノートオフ
void note_off(unsigned char ch);
//音色セット
void inst_set(unsigned char ch, unsigned char inst);

// MML演奏用関数/////////////////////////////////////
//簡易MMLデータより音を鳴らす
void mml_play(unsigned char *play_data, unsigned int len);
//簡易mmlデータ1バイト取得
int get_mml_data(unsigned char *mml_data, unsigned int len, unsigned int pos);
// mmlコマンド1つ取得
void get_mml_com(unsigned char *mml_data, unsigned int len, int *pos, int *ret_data);
// mmlデータから数値を返す
int get_mml_num(unsigned char *mml_data, unsigned int len, int *pos, int *dp_num);
// mmlデータ配列内のトラック数をカウントする。(「;」の数)
int get_mml_tr_num(unsigned char *mml_data, unsigned int len);
// mmlデータ配列内のトラック先頭位置配列に、トラック数分の先頭位置を入れる
int get_mml_tr_pos(unsigned char *mml_data, unsigned int len, unsigned int tr_max, int *pos_tr);

//タイマー割り込み~~~~~~~~~~~~~~~~~~~~~~~~~
void tim1()
{
  //ドラム制御用
  if (drum_set == 0)
  { //ドラムパラメータ変更中のとき、何もしない
    for (int i = 0; i < (CH / 3); i++)
    {
      if (drumint[i] == 1)
      { //ドラム発音中
        if (drumint_t[i] == 0)
        { //最初の一回だけ実行
          if (intwr != 1)
          { //書き込み中か?
            sn76489an_write_1(i, 6, 0x04 | (0x03 & drum_freq[i])); //ノイズ音程セット(ホワイトノイズ限定)
            if (drum_veldo[i] < 0)
            {
              drumint_vel[i] = 1; //リバースシンバルとか
            }
            else
            {
              drumint_vel[i] = drumvel;
            }
            sn76489an_write_1(i, 7, 15 - (drumint_vel[i] >> 3)); //ノイズ音量セット
            drumint_t[i] = 1;   //トグルセット
          }
          else
          { //書き込み中だと次の割り込みで実行
         
          }
        }
        else if ((drumint_veldo_lef[i] >= 30) && (drum_veldo[i] != 0))
        {
          drumint_vel[i] -= drum_veldo[i];
        }
        if ((intwr != 1) && (drumint_t[i] == 1) && (drumint_veldo_lef[i] >= 30) && (drum_veldo[i] != 0))
        { //音量制御
          drumint_veldo_lef[i] = 0;
          if ((0 >= drumint_vel[i]) || (drumvel < drumint_vel[i]))
          {                                //出力停止
            drumint[i] = 0;                //ドラムタイマー無効
            sn76489an_write_1(i, 7, 0x0F); //ノイズ音出力停止
          }
          else
          {
            sn76489an_write_1(i, 7, 15 - (drumint_vel[i] >> 3)); //ノイズ音量セット
          }
        }
        if (drumint_cm[i] <= drumint_c[i])
        {                 //出力停止
          drumint[i] = 0; //ドラムタイマー無効
        }
        if ((drumint_t[i] == 1))
        {
          drumint_c[i]++;
        }
      }
      else
      { //ドラム発音停止
        if (drumint_t[i] == 1)
        {                 //最初の一回だけ実行
          if (intwr != 1) //書き込み中か?
          {
            sn76489an_write_1(i, 7, 0x0F); //ノイズ音出力停止
            drumint_c[i] = 0;              //カウントリセット
            drumint_t[i] = 0;              //トグルリセット
          }
          else
          { //書き込み中だと次の割り込みで実行
          }
        }
      }
      drumint_veldo_lef[i]++;
    }
  }
  // MML演奏用
  if (mml_time_cnt_en == 1)
  {
    // MML演奏用時間カウント有効の時、カウント
    mml_time_ms++; // 1msごとのカウント
    // mml_time = mml_time_ms * (9*2*8192.0 * tempo / (120.0 * 2000.0));//下式と等価
    mml_time = mml_time_ms * (3 * 128.0 * tempo / 625.0); // msをMMLカウンタ値に変換(トラックごとの演奏ずれをできるだけ抑制するため)
  }
}

//セットアップ
void setup()
{
  //デバッグメッセージ用
  Serial.begin(SERIALSPEED); //シリアル通信開始
  Serial.print("\r\npwr_on_ok\r\n");
  //ポート設定
  // out_put

  PORTD = 0x00; // pin2~pin7_SN76489AN:D7(LSB)~D2
  DDRD = 0xFC;  // pin2~pin7_SN76489AN:D7(LSB)~D2

  PORTB = 0x00; // pin8~pin9_SN76489AN:D1~D0(MSB)
  DDRB = 0x03;  // pin8~pin9_SN76489AN:D1~D0(MSB)

  PORTC = 0x00; // pin14~pin16_SN76489AN:CS_BIT0~CS_BIT2
  DDRC = 0x07;  // pin14~pin16_SN76489AN:CS_BIT0~CS_BIT2

  pinMode(10, OUTPUT);    // pin10_SN76489AN:~WE
  digitalWrite(10, HIGH); // pin10_SN76489AN:~WE

  // SN76489AN発音用変数初期化
  for (int i = 0; i < CH; i++)
  {
    sn76489_vel[i] = 0;
  }

  // SN76489ANキーオフ
  for (int i = 0; i < CH; i++)
  {
    note_off(i);
  }

  // SN76489ANノイズ音量を0にする
  for (int i = 0; i < (CH / 3); i++)
  {
    sn76489an_write_1(i, 7, 15);
  }

  // MML演奏用タイマー
  Timer1.initialize((int)((float)(2000 * (XTAL / 16000000.0)))); // 1000μs毎にtim1( )割込み関数を呼び出す
  Timer1.attachInterrupt(tim1);                                  // タイマー割り込み開始
}

void loop()
{
  while (1)
  {
    // MML演奏
    mml_play(mml_data, mml_data_len);
    delay(2000);
  }
}

// SN76489ANへ書き込み(第一バイト)
void sn76489an_write_1(unsigned char cs, unsigned char adr, unsigned char data)
{
  intwr = 1; //書き込み中のため書き込み要求禁止

  PORTB |= 0x04; // digitalWrite(10, HIGH);//pin10_SN76489AN:~WE

  //モード(第一バイト)
  PORTB |= 0x02; // digitalWrite(9, HIGH);//pin9_SN76489AN:D0(mode)=H

  //アドレスのセット
  PORTB = (PORTB & 0xFE) | (0x01 & (adr >> 2)); // pin8_SN76489AN:D1(AA2)
  PORTD = (PORTD & 0x3F) | (adr << 6);          // pin7,6_SN76489AN:D2(AA1),D3(AA0)

  //データのセット
  PORTD = (PORTD & 0xC3) | (0x3C & (data << 2)); // pin2~5_SN76489AN:D7(DD0)~D4(DD3)

  //チップセレクト
  PORTC = (PORTC & 0xF8) | (0x07 & cs); // pin14~pin16_SN76489AN:CS_BIT0~CS_BIT2

  delayMicroseconds(10);
  //書き込み
  PORTB &= 0xFB; // digitalWrite(10, LOW);//pin10_SN76489AN:~WE
  delayMicroseconds(10);
  PORTB |= 0x04; // digitalWrite(10, HIGH);//pin10_SN76489AN:~WE
  intwr = 0;     //書き込み要求許可
}
// SN76489ANへ書き込み(第二バイト)
inline void sn76489an_write_2(unsigned char cs, unsigned char data)
{
  unsigned char portb = PORTB;
  intwr = 1;     //書き込み中のため書き込み要求禁止
  portb |= 0x04; // digitalWrite(10, HIGH);//pin10_SN76489AN:~WE

  //モード(第二バイト)
  portb &= 0xFD; // digitalWrite(9, LOW);//pin9_SN76489AN:D0(mode)=L

  PORTB = portb;

  //データのセット
  PORTD = (PORTD & 0x03) | (data << 2); // pin2~5_SN76489AN:D7(DD4)~D2(DD9)

  //チップセレクト
  PORTC = (PORTC & 0xF8) | (0x07 & cs); // pin14~pin16_SN76489AN:CS_BIT0~CS_BIT2

  delayMicroseconds(10);
  //書き込み
  portb &= 0xFB; // digitalWrite(10, LOW);//pin10_SN76489AN:~WE
  PORTB = portb;
  delayMicroseconds(10);
  portb |= 0x04; // digitalWrite(10, HIGH);//pin10_SN76489AN:~WE
  PORTB = portb;
  intwr = 0; //書き込み要求許可
}

//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum)
{
  if (ch != 9)
  { //ドラムチャンネル以外
    // sn76489an_write_2(ch / 3, tone_data[notenum] >> 4);
    sn76489an_write_1(ch / 3, (ch % 3) << 1, tone_data[notenum]);
    sn76489an_write_2(ch / 3, tone_data[notenum] >> 4);
  }
  else
  { //ドラムチャンネル
    drum_note_num = notenum;
  }
}

//メインの音量とパンのセット
void main_vel_pan_set(unsigned char vel, unsigned char pan)
{
  //何もしません。
}

//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel, unsigned char pan)
{
  // SN76489ANはステレオ出力では無いため、パンの制御はしません。
  // velの入力範囲は0~127(0~B01111111)ですが、SN76489ANは4bit値でなければならないため、シフトする。
  sn76489_vel[ch] = vel >> 3;

  //ドラムパートの場合
  if(ch == 0x09){
    drumvel = vel;
  }
}

//チャンネルノートオン
void note_on(unsigned char ch)
{
  if (ch != 9)
  { //ドラムチャンネル以外
    // SN76489ANはアッテネータのレジスタでノートオン・オフを決めます。
    sn76489an_write_1(ch / 3, ((ch % 3) << 1) + 1, 0x0F - sn76489_vel[ch]);
  }
  else
  { //ドラムチャンネル
    drum_set = 1;
    switch (drum_note_num)
    {
    case 32:
    case 21:
      drum_veldo[0] = (char)(0.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[0] = 0;   //リセット
      drum_freq[0] = 0;   //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[0] = 10; //時間
      drumint_vel[0] = drumvel;//ドラム初期ベロシティ
      drumint[0] = 1;
      break;
    case 25: //小
      if (recym_en == 0)
      {                   //リバースシンバル非発音中の時発音
        drum_veldo[0] = (char)(80.0 * ((float)drumvel / 127.0));//減衰量
        drumint_t[0] = 0; //リセット
        drum_freq[0] = 2;  //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
        drumint_cm[0] = 5; //時間
        drumint_vel[0] = drumvel;//ドラム初期ベロシティ
        drumint_veldo_lef[0] = 0;
        drumint[0] = 1;
      }
      break;
    case 27: //中
      if (recym_en == 0)
      {                   //リバースシンバル非発音中の時発音
        drum_veldo[0] = (char)(80.0 * ((float)drumvel / 127.0));//減衰量
        drumint_t[0] = 0; //リセット
        drum_freq[0] = 1;  //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
        drumint_cm[0] = 5; //時間
        drumint_vel[0] = drumvel;//ドラム初期ベロシティ
        drumint_veldo_lef[0] = 0;
        drumint[0] = 1;
      }
      break;
    case 33:
    case 35:
    case 36: //バス
      if (recym_en == 0)
      { //リバースシンバル非発音中の時発音
        drum_veldo[0] = (char)(0.0 * ((float)drumvel / 127.0));//減衰量
        drumint_t[0] = 0;   //リセット
        drum_freq[0] = 2;   //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
        drumint_cm[0] = 10; //時間
        drumint_vel[0] = drumvel;//ドラム初期ベロシティ
        drumint[0] = 1;
      }

      break;
    case 31: //スネア(ワイヤー)
      drum_veldo[1] = (char)(60.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[1] = 0;   //リセット
      drum_freq[1] = 1;   //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[1] = 15; //時間
      drumint_vel[1] = drumvel;//ドラム初期ベロシティ
      drumint[1] = 1;
      break;
    case 34:
    case 38:
    case 40: //スネア
      drum_veldo[1] = (char)(60.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[1] = 0;   //リセット
      drum_freq[1] = 0;   //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[1] = 15; //時間
      drumint_vel[1] = drumvel;//ドラム初期ベロシティ
      drumint[1] = 1;
      break;

    case 49:            //シンバル
      drumint_t[0] = 0; //リセット
      drum_veldo[0] = (char)(8.0 * ((float)drumvel / 127.0));//減衰量
      drum_freq[0] = 1;     //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[0] = 2500; //時間
      drumint_vel[2] = drumvel;//ドラム初期ベロシティ
      drumint[0] = 1;
      break;
    case 57:            //シンバル
      drumint_t[0] = 0; //リセット
      drum_veldo[0] = (char)(8.0 * ((float)drumvel / 127.0));//減衰量
      drum_freq[0] = 0;     //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[0] = 2500; //時間
      drumint_vel[2] = drumvel;//ドラム初期ベロシティ
      drumint[0] = 1;
      break;
    case 42:            //クローズハイハット
      drum_veldo[2] = (char)(80.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[2] = 0; //リセット
      drum_freq[2] = 0;  //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[2] = 5; //時間
      drumint_vel[2] = drumvel;//ドラム初期ベロシティ
      drumint_veldo_lef[2] = 0;
      drumint[2] = 1;

      break;
    case 44:            //足ハイハット
      drum_veldo[2] = (char)(80.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[2] = 0; //リセット
      drum_freq[2] = 0;   //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[2] = 15; //時間
      drumint_vel[2] = drumvel;//ドラム初期ベロシティ
      drumint_veldo_lef[2] = 0;
      drumint[2] = 1;

      break;
    case 46:            //オープンハイハット
      drum_veldo[2] = (char)(40.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[2] = 0; //リセット
      drum_freq[2] = 0;    //周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[2] = 250; //時間
      drumint_vel[2] = drumvel;//ドラム初期ベロシティ
      drumint_veldo_lef[2] = 0;
      drumint[2] = 1;

      break;
    case 127: //リバースシンバル
      recym_en = 1;//リバースシンバル有効
      drum_veldo[0] = (char)(-10.0 * ((float)drumvel / 127.0));//減衰量
      drumint_t[0] = 0; //リセット
      drum_freq[0] = 1;//周波数(SN76489ANは、0[高],1[中],2[低]のみ選択可能)
      drumint_cm[0] = 5000; //時間

      drumint[0] = 1;
      break;
    default:
      //発音無し
      break;
    }
    drum_set = 0;
  }
}

//チャンネルノートオフ
void note_off(unsigned char ch)
{
  if (ch != 9)
  { //ドラムチャンネル以外
    // SN76489ANはアッテネータのレジスタでノートオン・オフを決めます。
    sn76489an_write_1(ch / 3, ((ch % 3) << 1) + 1, 0x0F);
  }
  else
  {
    //リバースシンバル
    if (drum_note_num == 127)
    {
      recym_en = 0;
    }
  }
}

//音色セット
void inst_set(unsigned char ch, unsigned char inst)
{
  // SN76489ANは矩形波のみの出力のため、音色制御はしません。
}

// MML演奏用関数/////////////////////////////////////

//簡易MMLデータより音を鳴らす
void mml_play(unsigned char *play_data, unsigned int len)
{

  int mml_com[4] = {0, 0, 0, 0}; // mmlコマンド取得用[コード, 数値1, 時間, 数値2(音長表現のドットの数)]

  int mml_st[MML_MAX_TR];          // mml演奏状態(0でmml読み取り終了、1でmml読み取り中)、チャンネルごとに保存
  char mml_tie[MML_MAX_TR];        //タイ有効無効フラグ[^]
  char mml_oct[MML_MAX_TR];        //オクターブ[o]
  char mml_def_dly[MML_MAX_TR];    //デフォルト音調[l]
  char mml_pan[MML_MAX_TR];        //パン[p]
  char mml_vel[MML_MAX_TR];        //ベロシティ[v]
  double mml_next_tim[MML_MAX_TR]; //次のMMLコマンド実行時間(絶対時間)
  int mml_st_sum = 0;              //トラック演奏状態チェック用(フラグを加算して0になったらすべてのトラックの演奏が終了)

  ////////////////トラック数の取得と各トラック先頭位置の取得//////
  unsigned int tr_pos[MML_MAX_TR];                                         //トラック先頭位置保存用(先頭位置)
  int tr_num = get_mml_tr_pos(mml_data, mml_data_len, MML_MAX_TR, tr_pos); //各トラック先頭位置をtr_posに入れ、トラック数を返す

  //トラックごとの配列初期設定
  for (int mml_setup = 0; mml_setup < tr_num; mml_setup++)
  {
    mml_st[mml_setup] = 1;                                          // mml演奏状態フラグ(0でmml読み取り終了、1でmml読み取り中)
    mml_tie[mml_setup] = 0;                                         //タイ状態有効無効フラグ
    mml_oct[mml_setup] = 4;                                         //オクターブ
    mml_def_dly[mml_setup] = 4;                                     //デフォルト音調(四分音符)
    mml_next_tim[mml_setup] = 0;                                    //最初のMMLコマンドは無条件で読み取る。
    mml_pan[mml_setup] = 64;                                        //パン[p]
    mml_vel[mml_setup] = 127;                                        //ベロシティ[v]
    vel_pan_set(mml_setup, mml_vel[mml_setup], mml_pan[mml_setup]); //パンと音量の仮設定
  }
  // Serial.print("tr_num:");//デバッグ用
  // Serial.print(tr_num);//デバッグ用
  // Serial.print("\r\n");//デバッグ用

  mml_time_ms = 0;     // MML演奏用時間msカウンタ初期化
  mml_time_cnt_en = 1; // MML演奏用時間カウント_開始
  while (1)
  { //すべてのトラックが演奏終了のとき、mml演奏関数終了

    mml_st_sum = 0; //トラック演奏状態チェック用初期化
    //トラックごと処理

    for (int mml_st_tr = 0; mml_st_tr < tr_num; mml_st_tr++)
    {
      // Serial.print("mml_st[mml_st_tr]:");//デバッグ用
      // Serial.print(mml_st[mml_st_tr]);//デバッグ用
      // Serial.print("\r\n");//デバッグ用
      if (mml_st[mml_st_tr] == 0)
      { //演奏が終了したトラックは無視
        continue;
      }

      // Serial.print("mml_time:");//デバッグ用
      // Serial.print(mml_time);//デバッグ用
      // Serial.print("mml_next_tim[mml_st_tr]:");//デバッグ用
      // Serial.print(mml_next_tim[mml_st_tr]);//デバッグ用
      // Serial.print("\r\n");//デバッグ用

      if ((unsigned long)mml_next_tim[mml_st_tr] <= (unsigned long)mml_time)
      { //次回読み取り時間カウンタが時間カウンタの値以上になったらMMLコードを読み取る。
        ////////////////////////////

        // mmlコマンド取得(pos自動インクリメント)
        get_mml_com(play_data, len, (tr_pos + mml_st_tr), mml_com); //返しは、[コード, 数値1, 時間, 数値2(音長表現のドットの数)]の配列ポインタ
        if (mml_com[2] == -2)
        { // MMLの音調が省略されていたらデフォルト音長をセット
          mml_com[2] = mml_def_dly[mml_st_tr];
        }
        // MMLコマンド表示(多少不安定になります)//デバッグ用
        /*Serial.print("ch:");
        Serial.print(mml_st_tr);
        Serial.print("_pos:");
        Serial.print(tr_pos[mml_st_tr]);
        Serial.print("_");
        Serial.write(mml_com[0]);
        Serial.print(mml_com[1]);
        Serial.print("_dlytim:");
        Serial.print(mml_com[2]);
        Serial.print("_dot:");
        Serial.print(mml_com[3]);
        Serial.print("\r\n");*/

        switch (mml_com[0])
        {
        case 'n': //ノートオン
          if (mml_tie[mml_st_tr] == 0)
          {                                                                                    //タイ無効の時音を鳴らす
            ptc_set(mml_st_tr, ((mml_oct[mml_st_tr] + def_oct[mml_st_tr]) * 12) + mml_com[1]); //チャンネル, 音程

            note_on(mml_st_tr); //ノートオン
          }
          mml_tie[mml_st_tr] = 0; //タイ無効
          break;
        case 'r': //ノートオフ
          note_off(mml_st_tr);
          break;
        case 'o': //オクターブセット
          mml_oct[mml_st_tr] = mml_com[1];
          break;
        case '>': //オクターブ下げ
          mml_oct[mml_st_tr]--;
          break;
        case '<': //オクターブ上げ
          mml_oct[mml_st_tr]++;
          break;
        case '^':                 //タイ
          mml_tie[mml_st_tr] = 1; //タイ有効
          break;
        case 'l':                              //デフォルト音長
          mml_def_dly[mml_st_tr] = mml_com[1]; //音長
          break;
        case 'v': //ベロシティ
          mml_vel[mml_st_tr] = mml_com[1];
          vel_pan_set(mml_st_tr, mml_vel[mml_st_tr], mml_pan[mml_st_tr]); //チャンネル, 音量, パン
          break;
        case 'p': //パン
          mml_pan[mml_st_tr] = mml_com[1];
          vel_pan_set(mml_st_tr, mml_vel[mml_st_tr], mml_pan[mml_st_tr]); //チャンネル, 音量, パン
          break;
        case 't': //テンポ
          tempo = mml_com[1];
          break;
        case '@': //音色変更
          inst_set(mml_st_tr, mml_com[1]);
          break;
        case '\n':                //無命令、数字のみの場合(タイ)
          mml_tie[mml_st_tr] = 0; //タイ無効
          break;
        case ';':                //トラック終端
          mml_st[mml_st_tr] = 0; //現在のトラックは演奏終了
          break;
        default:
          break;
        }

        //時間計算
        if (mml_com[2] == 0)
        {
          //待ち時間なし
        }
        else
        {
          // delay(500);
          //待ち時間計算(ms)
          double dly_tim_dot_sum = 0;
          for (int d_time = 0; d_time <= mml_com[3]; d_time++) // mmlのドットの数分ディレイを入れる
          {
            dly_tim_dot_sum += (double)(9.0 * 8192.0 / (mml_com[2] * pow(2, d_time)));
            // delay((int)((2000.0 / (mml_com[2]*pow(2, d_time))) * (120.0 / tempo)));
          }
          //次回のMMLコマンド実行時間を加算する
          mml_next_tim[mml_st_tr] += dly_tim_dot_sum;
        }
        ////////////////////////////
      }

      mml_st_sum += mml_st[mml_st_tr]; //トラック演奏状態チェック用(フラグを加算して0になったらすべてのトラックの演奏が終了)
                                       // Serial.print("mml_st_sum:");//デバッグ用
                                       // Serial.print(mml_st_sum);//デバッグ用
                                       // Serial.print("\r\n");//デバッグ用
    }
    //トラック演奏状態チェック
    if (mml_st_sum == 0)
    {        //すべてのトラックが演奏終了のとき、mml演奏関数終了
      break; // mml演奏関数終了
    }
  }
  mml_time_cnt_en = 0; // MML演奏用時間カウント_停止
}
//簡易mmlデータ1バイト取得
int get_mml_data(unsigned char *mml_data, unsigned int len, unsigned int pos)
{
  // posは0~(len - 1)までを指定
  //データ範囲外で -1 を返す。

  if (pos >= len)
  {
    return -1;
  }
  // return mml_data[pos];    //MMLデータをSRAMに配置した時
  return pgm_read_byte_near(mml_data + pos); // MMLデータをPROGMEMに配置した時
}

// mmlコマンド1つ取得
//返しは、[コード, 数値1, 時間, 数値2(音長表現のドットの数)]の配列ポインタ
void get_mml_com(unsigned char *mml_data, unsigned int len, int *pos, int *ret_data)
{
  unsigned int pos_temp = *pos; //読み取り位置を記憶
  unsigned char temp_get_mml_byte;

  int ret_code = 0;
  int ret_num1 = 0;
  int ret_tim = 0;
  int ret_num2 = 0;

  switch (get_mml_data(mml_data, len, pos_temp))
  {
  case '0': //数字の場合、音長のみとして扱う
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    ret_code = (int)'\n';
    ret_tim = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case 'b': //
    ret_num1 += 2;
  case 'a': //
    ret_num1 += 2;
  case 'g': //
    ret_num1 += 2;
  case 'f': //
    ret_num1 += 1;
  case 'e': //
    ret_num1 += 2;
  case 'd': //
    ret_num1 += 2;
  case 'c': //
    if (get_mml_data(mml_data, len, pos_temp + 1) == '+')
    {
      pos_temp++;
      ret_num1 += 1; //半音上げ
    }
    else if (get_mml_data(mml_data, len, pos_temp + 1) == '-')
    {
      pos_temp++;
      ret_num1 -= 1; //半音下げ
    }
    pos_temp++;
    ret_code = 'n';
    ret_tim = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    if (ret_tim == 0)
    { //音長が省略されていたら
      ret_tim = -2;
    }
    else if (ret_tim == -3)
    { //音長が0だった場合、和音(ドラムのみ)
      ret_tim = 0;
    }
    break;
  case 'r': //ノートオフ
    ret_code = 'r';
    pos_temp++;
    ret_tim = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    if (ret_tim == 0)
    { //音長が省略されていたら
      ret_tim = -2;
    }
    break;
  case '<': //
    ret_code = (int)'<';
    pos_temp++;
    break;
  case '>': //
    ret_code = (int)'>';
    pos_temp++;
    break;
  case '^': //タイ
    ret_code = (int)'^';
    pos_temp++;
    break;
  case 'o': //音程
    ret_code = (int)'o';
    pos_temp++;
    ret_num1 = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case 'v': //ベロシティ
    ret_code = (int)'v';
    pos_temp++;
    ret_num1 = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case 'p': //ベロシティ
    ret_code = (int)'p';
    pos_temp++;
    ret_num1 = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case 'l': //デフォルト音長
    ret_code = (int)'l';
    pos_temp++;
    ret_num1 = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case '[': //ループ開始(使用不可)
    pos_temp++;
    break;
  case ']': //ループ終了(使用不可)
    pos_temp++;
    break;
  case 't': //テンポ
    ret_code = (int)'t';
    pos_temp++;
    ret_num1 = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case '@': //音色
    ret_code = (int)'@';
    pos_temp++;
    ret_num1 = get_mml_num(mml_data, len, &pos_temp, &ret_num2); // mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
    break;
  case ';': //終端
    ret_code = ';';
    // pos_temp=len;
    break;
  default: //未定義コマンドが来るとストップ
    break;
  }

  *pos = pos_temp; //戻し用現在の読み取り位置更新
  ret_data[0] = ret_code;
  ret_data[1] = ret_num1;
  ret_data[2] = ret_tim;
  ret_data[3] = ret_num2;
}

// mmlデータから数値を返す
// mmlデータ配列, mmlデータ配列の長さ, 読み取り位置のポインタ, ドットの数戻り値用ポインタ
int get_mml_num(unsigned char *mml_data, unsigned int len, int *pos, int *dp_num)
{
  //※読み取り位置は配列外になっても加算されます。
  unsigned int pos_temp = *pos; //読み取り位置を記憶
  int keta = 0;                 //読み取る数値の桁数(0で数字以外,1で1桁,2で2桁)の読み取り位置インクリメント用
  int ret_num = 0;              //戻り値用数字
  int ret_dp_num = 0;           //ドットの数戻り値用
  int mml_data_byte = get_mml_data(mml_data, len, pos_temp);

  //数値読み取り
  while ((mml_data_byte >= '0') && (mml_data_byte <= '9'))
  {
    // ret_num += (mml_data_byte - '0')*pow(10, keta);//数字と桁数の乗算値を加算
    if ((keta == 0) && (mml_data_byte == '0'))
    { //音長が0(数値の最初が0)のとき、-3を返す
      ret_num = -3;
    }
    else
    {
      ret_num = ret_num * 10 + (mml_data_byte - '0');
    }

    keta++;
    mml_data_byte = get_mml_data(mml_data, len, pos_temp + keta);
  }

  //ドットの数を数える
  mml_data_byte = get_mml_data(mml_data, len, pos_temp + keta);
  while (mml_data_byte == '.')
  {
    ret_dp_num++;
    mml_data_byte = get_mml_data(mml_data, len, pos_temp + keta + ret_dp_num);
  }

  *pos += keta + ret_dp_num; //現在の読み取り位置に読み取った数分、加算
  *dp_num = ret_dp_num;      //ドットの数書き換え
  return ret_num;
}

// mmlデータ配列内のトラック数をカウントする。(「;」の数)
int get_mml_tr_num(unsigned char *mml_data, unsigned int len)
{
  int get_data_temp = 0;
  unsigned int tr_cnt = 0; //トラック数カウント

  for (int c_tr = 0; c_tr < len; c_tr++)
  {

    get_data_temp = get_mml_data(mml_data, len, c_tr);
    if (get_data_temp == ';')
    {
      tr_cnt++; //「;」の数カウント
    }
    else if ((get_data_temp == 0) || (get_data_temp == -1))
    {
      //終端判定
      //文字列配列終端(0x00)か配列サイズ外でデータ終了
      break;
    }
  }
  return tr_cnt;
}

// mmlデータ配列内のトラック先頭位置配列に、トラック数分の先頭位置を入れる
// mmlデータ配列, mmlデータ配列の長さ, 取得トラック数, トラック先頭位置配列の戻り値用ポインタ
//戻り値は、先頭位置取得トラック数
int get_mml_tr_pos(unsigned char *mml_data, unsigned int len, unsigned int tr_max, int *pos_tr)
{
  int get_data_temp = 0;
  unsigned int tr_cnt = 0;          //トラック数カウント
  unsigned int get_temp_tr_pos = 0; //トラック先頭アドレス一時保存

  for (int c_tr = 0; c_tr < len; c_tr++)
  {

    get_data_temp = get_mml_data(mml_data, len, c_tr);
    if (get_data_temp == ';')
    {
      pos_tr[tr_cnt] = get_temp_tr_pos; //前回の「;」の次のアドレスを入れる
      get_temp_tr_pos = c_tr + 1;
      tr_cnt++; //「;」の数カウント
    }
    if ((get_data_temp == 0) || (get_data_temp == -1) || (tr_max <= tr_cnt))
    {
      //終端判定
      //文字列配列終端(0x00)か配列サイズ外でデータ終了
      break;
    }
  }
  return tr_cnt;
}






現在執筆中です。内容が変更する可能性があります。

0 件のコメント:

コメントを投稿