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段階必要となります。 ・第一バイト
・第二バイト
出力する周波数fは、 f = CLK / (32 * n) [Hz] CLK : SN76489ANの入力クロック周波数 n : このレジスタ値(n9(MSB)~n0(LSB)の10bit) となります。 |
||||||||||||||||||||||||||||||||
$1 |
Tone 1 Attenuation | トーン1の音量を決定します。 0~28dBアッテネートできます。
アッテネートレベルAは、 A = 2 * na [dB] na : このレジスタ値(na3(MSB)~na0(LSB)の4bit) ただし、na = 15(0xF)のとき、ミュート となります。 |
||||||||||||||||||||||||||||||||
$2 | Tone 2 Frequency |
トーン2の音程を決定します。 このレジスタへのデータの書き込みは、2段階必要となります。 ・第一バイト
・第二バイト
出力する周波数fは、
f = CLK / (32 * n) [Hz] CLK : SN76489ANの入力クロック周波数 n : このレジスタ値(n9(MSB)~n0(LSB)の10bit) となります。 |
||||||||||||||||||||||||||||||||
$3 | Tone 2 Attenuation | トーン2の音量を決定します。 0~28dBアッテネートできます。
アッテネートレベルAは、 A = 2 * na [dB] na : このレジスタ値(na3(MSB)~na0(LSB)の4bit) ただし、na = 15(0xF)のとき、ミュート となります。 |
||||||||||||||||||||||||||||||||
$4 | Tone 3 Frequency | トーン3の音程を決定します。 このレジスタへのデータの書き込みは、2段階必要となります。 ・第一バイト
・第二バイト
出力する周波数fは、 f = CLK / (32 * n) [Hz] CLK : SN76489ANの入力クロック周波数 n : このレジスタ値(n9(MSB)~n0(LSB)の10bit) となります。 |
||||||||||||||||||||||||||||||||
$5 | Tone 3 Attenuation | トーン3の音量を決定します。 0~28dBアッテネートできます。
アッテネートレベルAは、 A = 2 * na [dB] na : このレジスタ値(na3(MSB)~na0(LSB)の4bit) ただし、na = 15(0xF)のとき、ミュート となります。 |
||||||||||||||||||||||||||||||||
$6 | Noise Control | ノイズコントロールレジスタです。
bit0, bit1でノイズジェネレータのクロック源を選択します。 bit0 = H, bit1 = Lの場合、トーン3の音程レジスタの値でノイズ音程が決まります。
bit2 L : 周期的ノイズ H : ホワイトノイズ LSBから数えてbit0,1がノイズ周波数を決めて、bit2がノイズの種類を決めます。 ノイズ周波数は基本的に、高・中・低の3種類のみです。 トーン3の出力を犠牲もしくは、音程レジスタを共用することで、ノイズ周波数を自由に決めることができます。 |
||||||||||||||||||||||||||||||||
$7 | Noise Attenuation | ノイズの音量を決定します。 0~28dBアッテネートできます。
アッテネートレベル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のとき
D0(mode) = Hのとき、 D1(AA2)~D3(AA0)がアドレス指定レジスタ、D4(DD3)~D7(DD0)がデータレジスタとなります。 ・D0(mode) = Lのとき
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)に書き込むデータをセットします。
第一バイトの書き込みは「・矩形波周波数設定レジスタ以外のレジスタへの書き込み」の時と同様です。
第二バイトの書き込みは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 件のコメント:
コメントを投稿