2022年2月10日木曜日

SOUND8 音源ICの仕様


SOUND8 音源ICの仕様


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

デジットで販売された謎の音源IC「SOUND 8 © CMT 1983」が出物として販売されました。

1984年4月号と5月号のトランジスタ技術に、このICの使い方が紹介されていたので、取り上げてみます。

4月号では、6個の「SOUND 8」を組み合わせて同時発音数5のポリフォニック・キーボードを製作しているようです。


5月号では、Z80PIOと組み合わせてコンピュータ・ミュージック・ボードを製作しているようです。


・SOUND 8 の仕様

・キースキャンモードとトーンモードの2つのモード

・同時発音数 : 1チャンネル

・8種類の音色

・ディレイ、ビブラート

・6オクターブの半音階音楽発生


「SOUND 8」は、40ピンDIPのICです。
この音源ICは、組み込み音源用というよりは、キーボード用としての専用音源ICに近いです。

バス接続を想定して作られていないため、音程のセットや発音は、個別のピン
に信号を送る必要があります。


また、この音源ICは2つのモードがあります。
トーンモードでは、このIC1つにつき1チャンネルの音を発音できます。
同時発音数を稼ぎたい場合は、複数の「SOUND 8」を用意する必要があります。

キーボードモードでは、「8243」と組み合わせてキースキャン用ICとして振る舞います。
トーンモード用の「SOUND 8」と直接接続できるため、マイコンレスでポリフォニックキーボードの製作ができます。
キーボードモード用に1つにつき、トーンモード用として最大8つ組み合わせて使用できます。


・ピン配置

1984年4月号のトランジスタ技術の図1-1、表1-1、図1-4、表1-2を参考にしました。


・トーンモード
ピン番号 名称 I/O 機能
1 ~SCAN/TONE I
モード切り替え端子です。

L : スキャンモード
H : トーンモード

トーンモードではHにします。
2 XTAL1 I? クロック入力端子です。
標準で11MHzを入力するようです。

※要検証
3 XTAL2 O? クロック出力端子です。

※要検証
4 ~RESET I リセットピンです。
5 UNUSED - 未使用端子です。
オープンにしてください。
6 ~INT I 割り込み端子(キーのオンオフのトリガクロック)です。
この端子がH→Lになるタイミングで、
12~17番ピン「NTC0~NTC5」、
18,19番ピン「VIB0,VIB1」、
27~29番ピン「TMB0~TMB2」、
30~34番ピン「TP0~TP4」、
39番ピン「ON/~OFF」の状態を読み取ってアタックまたはディケイを開始します。

39番ピン「ON/~OFF」の命令(ONまたはOFF)は同じもの連続して与えることは禁止されています。
7 VSS - GNDです。
8 UNUSED - 未使用端子です。
オープンにしてください。
9 UNUSED - 未使用端子です。
オープンにしてください。
10 UNUSED - 未使用端子です。
オープンにしてください。
11 CLK/15 O クロック入力端子に入力されたクロックの1/15に分周した周波数を出力します。
12 NTC0 I 音程コード入力端子です。
NTC0~NTC5に0x00から0x3Eを入力すると、
65.41Hz(C1)~2349.32Hz(D6)の音程をセットできるようです。

出力音程周波数fは、

  f = 65.4064 * XTAL * (2^(NTC /12)) / 11M
    = 5.946045μ * XTAL * (2^(NTC /12))  [Hz]

  XTAL : 水晶振動子の発振周波数
となります。
13 NTC1 I 同上
14 NTC2 I 同上
15 NTC3 I 同上
16 NTC4 I 同上
17 NTC5 I 同上
18 VIB0 I ビブラート、ディレイビブラートの設定端子です。

VIB1 VIB0 ビブラートの種類
0 0 ビブラートなし
0 1通常のビブラート
1 0 ディレイビブラート
1 1 ディレイビブラート

19 VIB1 I 同上
20 VSS - GNDです。
21 DAC0 O オーディオ出力端子です。
出力はデジタル値で、2の補数表現の8ビットです。
アナログ出力するにはDACを使う必要があります。

DAC0~DAC7には、「DAC08」などのDACを接続します。
22 DAC1 O 同上
23 DAC2 O 同上
24 DAC3 O 同上
25 UNUSED - 未使用端子です。
オープンにしてください。
26 VDD - +5V電源端子です。
27 TMB0 I 音色設定端子です。

TMB2 TMB1 TMB0 ビブラートの種類
0 0 0 フルート
0 0 1 パイプオルガン
0 1 0 クラリネット
0 1 1 オーボエ
1 0 0 トランペット
1 0 1 ストリング
1 1 0 ピアノ1
1 1 1 ピアノ2

28 TMB1 I 同上
29 TMB2 I 同上
30 TP0 I 移調入力端子です。
TP0~TP4の5ビットで移調を入力します。
2の補数表現で入力するため、-16 ~ +15の音程の移動ができます。

発音する音程は、(NTC0~NTC5)の6ビットと(TP0~TP4)の5ビットの和で決まります。
31 TP1 I 同上
32 TP2 I 同上
33 TP3 I 同上
34 TP4 I 同上
35 DAC4 O オーディオ出力端子です。
出力はデジタル値で、2の補数表現の8ビットです。
アナログ出力するにはDACを使う必要があります。

DAC0~DAC7には、「DAC08」などのDACを接続します。
36 DAC5 O 同上
37 DAC6 O 同上
38 DAC7 O 同上
39 ON/~OFF I キーオン、キーオフ制御端子です。
H : キーオン(アタック開始)
L : キーオフ(ディケイ開始)

この端子にセットした後に6番ピン(~INT)ピンを
H→Lにすると
アタックまたはディケイを開始します。

同じ命令(ONまたはOFF)を連続して与えることは禁止されています。
40 VCC - +5V電源端子です。







・キースキャンモード
ピン番号 名称 I/O 機能
1 ~SCAN/TONE I
モード切り替え端子です。

L : スキャンモード
H : トーンモード

スキャンモードではLにします。
2 XTAL1 I? クロック入力端子です。
標準で11MHzを入力するようです。

※要検証
3 XTAL2 O? クロック出力端子です。

※要検証
4 ~RESET I リセットピンです。
5 UNUSED - 未使用端子です。
オープンにしてください。
6 VCC1 - +5V電源へ接続します。
7 VSS - GNDです。
8 UNUSED - 未使用端子です。
オープンにしてください。
9 UNUSED - 未使用端子です。
オープンにしてください。
10 UNUSED - 未使用端子です。
オープンにしてください。
11 CLK/15 O クロック入力端子に入力されたクロックの1/15に分周した周波数を出力します。
12 NTC0 O 音程コード出力端子です。
キー入力に応じてNTC0~NTC5から0x00から0x3E(65.41Hz(C1)~2349.32Hz(D6))が出力されます。
13 NTC1 O 同上
14 NTC2 O 同上
15 NTC3 O 同上
16 NTC4 O 同上
17 NTC5 O 同上
18 UNUSED - 未使用端子です。
オープンにしてください。
19 ON/~OFF O キーオン、キーオフ制御出力端子です。
キー入力に応じてキーオン、キーオフ信号が出力されます。
20 VSS - GNDです。
21 P20 I/O 8243へ接続するためのバスです。
22 P21 I/O 同上
23 P22 I/O 同上
24 P23 I/O 同上
25 PROG O 8243出力ストローブ端子です。
26 VDD - +5V電源端子です。
27 ~INT0 O 割り込み出力端子(キーのオンオフのトリガクロック)です。
キー入力に応じてINT0~INT7がH→Lになります。
28 ~INT1 O 同上
29 ~INT2 O 同上
30 ~INT3 O 同上
31 ~INT4 O 同上
32 ~INT5 O 同上
33 ~INT6 O 同上
34 ~INT7 O 同上
35 CH0 I 最大同時発音数設定端子です。
トーンモードのSOUND8を接続する個数を決めます。

CH0~CH2に0x0~0x7を入力することで、
最大同時発音数を1~8個に設定ができます。
36 CH1 I 同上
37 CH2 I 同上
38 UNUSED - 未使用端子です。
オープンにしてください。
39 UNUSED - 未使用端子です。
オープンにしてください。
40 VCC - +5V電源端子です。





・タイミング図

・トーンモード
「SOUND8」を音源ICとして使用する場合、1番ピン「~SCAN/TONE」をHします。

-キーオン-




-キーオフ-
キーオンまたはキーオフしたい場合は、
12~17番ピン「NTC0~NTC5」(音程コード入力)、
18,19番ピン「VIB0,VIB1」(ビブラート、ディレイビブラートの設定端子)、
27~29番ピン「TMB0~TMB2」(音色設定端子)、
30~34番ピン「TP0~TP4」(移調入力端子)、
39番ピン「ON/~OFF」(キーオン、キーオフ制御端子)らに任意のデータをセットします。

その後、6番ピン「~INT」(割り込み端子(キーのオンオフのトリガクロック))をLにします。
「~INT」がLとなる時間は240μsから5msの間となるようにする必要があるようです。



-連続してキーオン、キーオフする場合-
連続してキーオンとキーオフを行う場合、20msより多く時間を空けなければいけないようです。



・使い方

この音源ICは、おおまかに2通りの使い方があります。

1つは音源ICとして、外部のマイコンによる制御で発音します。
音源ICの個数 = 最大同時発音数となります。

もう1つの使い方は、「SOUND8」の1つをキースキャンモードとして使用して、マイコンレスポリフォニックキーボードとして利用することです。
音源ICの個数-1 = 最大同時発音数(1~8音)となります。

どちらの用途でも、同時発音数をたくさん稼ぎ場合、たくさんのICが必要になります。
さらに、トーンモードの「SOUND8」1つにつき8ビットパラレルDACも1つ必要になります。(CPLDなどで加算器などを作れば、複数の入力をまとめて1つのDACへ出力することもできそうです。)
DACは、頑張ってR2Rラダーを組んでもよいでしょう。


-音源ICとして、外部のマイコンによる制御で発音する場合-


・回路図
「SOUND8」の一番簡単な使い方です。(同時発音数:1)
出力にバッファを介してLPFを入れてください。


「SOUND8」を8個使った場合です。「~INT」ピン以外は共通にします。
(ファンアウトに注意)
「SOUND8」を複数使用する場合、クロックを共通化することもできます。

「SOUND8」の音声出力はデジタル値のためDACが必要です。2の補数表現の8ビットのパラレルで出力されます。(-128 ~ +127)

DACは「DAC08」や「DAC1408」を使います。
ただしこれらのDACは、(0 ~ +255)の値で入力しなければならないため、2の補数表現(-128(B1000000) ~ +127(01111111))から(0(B00000000) ~ +255(11111111))へ変換する必要があります。
128(10000000)を加算してあげればよさそうです。

実際に128を加算するとMSB部分が反転するのみで0~6ビットの部分は変化しません。
よって、MSBの部分のみインバータ(74HC04等)をかましてあげるように接続します。

R2Rラダーを組んでA/D変換する場合も同様です。
出力オフセットが電源電圧の半分となりますが、バッファ後にカップリングコンデンサを接続して直流をカットするようにします。

DACの出力後には、LPFを接続します。トラ技のポリフォニックキーボードの回路(図1-11)では遮断周波数が5.627kHzになるようなアクティブLPFを接続しています。

このことから、サンプリング周波数が11kHz付近にあると予想できます。よって発振周波数の1/1000の周波数がサンプリング周波数となるでしょう。(要検証)
水晶振動子が11MHzでない場合の設計遮断周波数fcは、

fc = (XTAL / 1000 ) / 2
   = XTAL / 2000 [Hz]

XTAL : 水晶振動子の発振周波数


-制御-
「NTC0」~「NTC6」に鳴らしたい音程、「VIB0」~「VIB1」にビブラートの設定、「TMB0」~「TMB2」に楽器の種類の設定、「ON/~OFF」にHをセットして
最後に「~INT」を240μs<t<5msの期間HからLにすると、キーオンします。
「SOUND8」にはハードウエアエンベロープ機能があるため、楽器の種類に応じて時間的に音量が変化します。

「ON/~OFF」にLをセットして「~INT」を240μs<t<5msの期間HからLにすると、キーオフします。

音量の制御はできないので、音量制御したい場合は外部にデジタル制御できるアッテネータICを接続してください。



-キースキャン用と発音用に分けて使用する場合-

最大同時発音数8のポリフォニックキーボードです。


キースキャン用に「SOUND8」を1個追加することで、最大同時発音数が8つのキーボードが作れます。
インテルのMCS-48用のIOエキスパンダIC「8243」とダイオードキーマトリクスを組み合わせて使用します。

発音部分の回路は、「SOUND8」を音源ICとして使用した時のと同じ回路で良いです。



・使用例

最後にMML演奏プログラム例をのせておきます。(プログラムの動作はチェックしていません。)



・回路図



・Arduinoプログラム例
//sound8でMML演奏プログラム(動作未チェック)
//©oy
//https://oykenkyu.blogspot.com/2022/02/sound8.html
//手元にSOUND8が無いため、このプログラムが正常に動作するかはわかりません。

#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 8//sound8最大チャンネル数
#define MML_MAX_TR 8//MMLトラック数

#define INST_FLUTE 0//ピアノ
#define INST_PIPEORGAN 1//パイプオルガン
#define INST_CLARINET 2//クラリネット
#define INST_OBOE 3//オーボエ
#define INST_TRUMPPET 4//トランペット
#define INST_STRINGS 5//ストリング
#define INST_PIANO1 6//ピアノ1
#define INST_PIANO2 7//ピアノ2

#define VIB_NO_VIB 0//ビブラート無し
#define VIB_NORMAL_VIB 1//通常ビブラート
#define VIB_DELAY_VIB 2//ディレイビブラート


//sound8_保存用
unsigned char s8_tone[CH];//音程
//音色
unsigned char s8_inst[CH] = {INST_PIANO1, INST_PIANO1, INST_PIANO1, INST_PIANO1, INST_PIANO1, INST_PIANO1, INST_PIANO1, INST_PIANO1};//楽器
//ビブラート設定
unsigned char s8_vib[CH] = {VIB_NO_VIB, VIB_NO_VIB, VIB_NO_VIB, VIB_NO_VIB, VIB_NO_VIB, VIB_NO_VIB, VIB_NO_VIB, VIB_NO_VIB};//ビブラート設定
//キーの状態保存
unsigned char s8_key[CH];//0:off, 1:on

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


//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;//テンポ




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

//mmlのトラック終端には必ず「;」を入れてください。
//使えるコマンド
//「;」             :トラック終端コマンド(トラック終端に必ず入れてください。)
//
//・全チャンネル(トラック)でパラメータを共有
//  「t」+ 数値:テンポを整数で指定
//
//・各チャンネル(トラック)でパラメータの独立指定が可能
//  「v」+ 数値      :ベロシティ(音量)を整数で指定
//  「p」+ 数値      :ベロシティ(音量)を整数で指定
//  「o」+ 数値      :デフォルトオクターブを整数で指定
//  「l」+ 数値      :デフォルト音長を整数で指定
//  「<」または「>」  :オクターブを上げる、下げる
//  「^」            :タイ(タイの次の音程コマンドでの発音は無視され、音長のみ取得します。)
//
//
//・音程コマンド
//  (音程)+(「+」または「-」)+(音長)+(「.」):音程は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分音符の長さ)
//  
//  

//よいまちカンターレ
const unsigned char PROGMEM mml_data[]=
"t171l16r1r1r8o7d8>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;l16r1r1r8o6d8>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;l16r1r1o3d8d8r4.>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.;l16r1r1r8o4a8r2r8a4.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;l16r1r1o7a24f+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.;l16r1r1o4d4r4.>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;l16r1r1r2r8o3f+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;l16r1r1o5d4r4.>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;";


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

//sound8発音用
//sound8へ書き込み
void s8_write(unsigned char ch, unsigned char on_off, unsigned char tone, unsigned char inst, unsigned char vib);
//音程のセット
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()//MML演奏用
{
  if(mml_time_cnt_en == 1){
    //MML演奏用時間カウント有効の時、カウント
    mml_time_ms++;//1msごとのカウント
    //mml_time = mml_time_ms * (9*8192.0 * tempo / (120.0 * 2000.0));//下式と等価
    mml_time = mml_time_ms * (3*64.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_SOUND8:NTC0~NTC5
  DDRD = 0xFC;  //pin2~pin7_SOUND8:NTC0~NTC5
 
  PORTB = 0x00;  //pin8~pin10_SOUND8:TMB0~TMB2   pin11,12_SOUND8:VIB0,1   pin13_SOUND8:ON_OFF
  DDRB = 0x2F;  //pin8~pin10_SOUND8:TMB0~TMB2   pin11,12_SOUND8:VIB0,1   pin13_SOUND8:ON_OFF
 
  PORTC = 0x00;  //pin14~pin17_SOUND8:INT_CS0~INT_CS3
  DDRC = 0x0F;  //pin14~pin17_SOUND8:INT_CS0~INT_CS3

  pinMode(19, OUTPUT);  //pin19_SOUND8:int
  digitalWrite(19, HIGH);//pin19_SOUND8:int

  //SOUND8発音用変数初期化
  for(int i = 0;i < CH;i++){
    s8_tone[i] = 0;
    //s8_inst[i] = 0;
    //s8_vib[i] = 0;
    s8_key[i] = 0;
  }
 

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

void loop()
{
 
  Serial.print("setup ok\r\n");
  while(1)
  {
    //MML演奏
    mml_play(mml_data, mml_data_len);
  }
}

//sound8へ書き込み
void s8_write(unsigned char ch, unsigned char on_off, unsigned char tone, unsigned char inst, unsigned char vib)
{
  digitalWrite(19, HIGH);//pin19_SOUND8:int
  //音程セット
  PORTD = (PORTD & 0x03) | (0xFC & (tone << 2));  //pin2~pin7_SOUND8:NTC0~NTC5
  //楽器、ビブラート、キーオンオフ
  PORTB = (PORTB & 0xE0) | (0x1F & ((0x07 & inst) | (vib << 3)));  //pin8~pin10_SOUND8:TMB0~TMB2   pin11,12_SOUND8:VIB0,1
  digitalWrite(13, on_off);//pin19_SOUND8:ON_OFF

  //SOUND8チップセレクト
  PORTC = (PORTC & 0xF0) | (0x0F & ch);  //pin14~pin17_SOUND8:INT_CS0~INT_CS4

  //INT
  digitalWrite(19, LOW);//pin19_SOUND8:int
  delayMicroseconds(300);
  digitalWrite(19, HIGH);//pin19_SOUND8:int
}



//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum)
{
  s8_tone[ch] = notenum;
}

//メインの音量とパンのセット
void main_vel_pan_set(unsigned char vel,unsigned char pan)
{
  //SOUND8は音量制御できないため、何もしません。
  //外部アッテネータなどで音量制御したい場合は、ここに記述してください。

}

//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel,unsigned char pan)
{
  //SOUND8は音量制御できないため、何もしません。
  //外部アッテネータなどで音量制御したい場合は、ここに記述してください。

}

//チャンネルノートオン
void note_on(unsigned char ch)
{
  while(s8_tone[ch] >= 63){//音程が高すぎて発音出来ない場合、オクターブ下げる
    s8_tone[ch] -= 12;
  }
  s8_write(ch, 1, s8_tone[ch], s8_inst[ch], s8_vib[ch]);
 
}

//チャンネルノートオフ
void note_off(unsigned char ch)
{
  while(s8_tone[ch] >= 63){//音程が高すぎて発音出来ない場合、オクターブ下げる
    s8_tone[ch] -= 12;
  }
  s8_write(ch, 0, s8_tone[ch], s8_inst[ch], s8_vib[ch]);
  //キーオンから20ms未満でキーオフしてはいけないので注意
}

//音色セット
void inst_set(unsigned char ch,unsigned char inst)
{
  s8_inst[ch] = inst;
 
}


//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] = 64;//ベロシティ[v]
  }
  //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]);//チャンネル, 音程
              if(s8_key[mml_st_tr] == 1){//SOUND8は連続してキーオンまたはキーオフすることを禁止しているので、キーオンだった場合キーオフしてからキーオンする。
                note_off(mml_st_tr);
              }
              note_on(mml_st_tr);//ノートオン
              s8_key[mml_st_tr] = 1;//key_on
            }
            mml_tie[mml_st_tr] = 0;//タイ無効
            break;
          case 'r'://ノートオフ
            if(s8_key[mml_st_tr] == 1){//SOUND8は連続してキーオンまたはキーオフすることを禁止しているので、キーオンだった場合のみキーオフする。
              note_off(mml_st_tr);
            }
            s8_key[mml_st_tr] = 0;//key_off
            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 ';'://トラック終端
            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 '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;
      }
      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);//数字と桁数の乗算値を加算
    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;
 
}







・参考資料

トランジスタ技術4月号
三橋康博   楽音用IC SOUND8の応用(前編)   pp429-437







0 件のコメント:

コメントを投稿