2022年3月20日日曜日

MSM5232の使い方


MSM5232の使い方

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



M5232
(MSM5232RS)
KORGのポリシンセ内蔵品バージョン


MSM5232RS (M5232)は、同時発音数が8つの4波形合成型の音源ICです。
(1985年4月号のトランジスタ技術の沖電気の広告では「音階発声用(音源)LSI」と表記されています。)

基音、2倍、4倍、8倍の周波数の波形を任意に合成して、発音します。
MSM5232単体では、この4種類の波形のON/OFFの組み合わせのみ制御できます。

よって、音作りは基本的にMSM5232に接続するコンデンサ(波形の決定)と抵抗(合成比率)によって行います。
KORGのシンセサイザでは、さらにアナログフィルタを追加して豊かな音色を作り出しています。

ハードウェアエンベロープ機能もあり、いろいろな音色を出すことができます。


この音源ICは、KORGのポリフォニックシンセサイザ等(SAS-20、POLY-800、PSS-50、EX-800)やTechnicsのSX-K450等に使われています。
また、一部のアーケードにも使用されていたようです。

この音源ICは一般にあまり出回っていないため、KORGのシンセなどを分解&部品取りなどをして入手するのが良いでしょう。
SAS-20の場合、ICソケットで実装されているため流用しやすくおすすめです。




・MSM5232の特徴


・4種波形合成器 x 8ch (ノイズ音用エンベロープチャンネルとすることも可能)
・42ピンDIP
・2種類のハードウェアエンベロープ("アタック・ディケイモード(サスティン=0)" or "アタック・リリースモード(サスティン=max)")
・±5V電源で駆動(単電源でも使用可)
・88種のピッチを出力可能
・原則音量の制御は不可(エンベロープを無効にすることで、外部アナログ入力により音量制御が可能)




・MSM5232のブロック図

KORG PSS-50のサービスマニュアルより抜粋





・ピン配置



ピン番号 名称 I/O 機能
1 solo 8' O
トーンジェネレータ7の4倍音ソロ出力端子です。
キーオン・キーオフの状態にかかわらず常に矩形波を出力します。
2 solo 16' O
トーンジェネレータ7の8倍音ソロ出力端子です。
キーオン・キーオフの状態にかかわらず常に矩形波を出力します。
3 VDD1 - 内部ロジック電源端子です。
+5Vの電源を用意してください。
4 D7 I データピンです。
5 D6 I データピンです。
6 D5 I データピンです。
7 D4 I データピンです。
8 D3 I データピンです。
9 D2 I データピンです。
10 D1 I データピンです。
11 D0 I データピンです。
12 ~WR I ライトイネーブルピンです。

「~WR」がLでかつ、「ALE」が
Lの期間にD0~D7の状態がラッチされます。
13 ALEI アドレスラッチイネーブルピンです。

「ALE」がHの期間にA0~A3の状態がラッチされます。

また、このピンのクロックをMSM5232の内部ロジックのステートクロックとして利用しています。
ですので、ALEのクロック周期は原則0.5μs以上10μs以内となるようにしてください。このクロック入力が無い場合、正常に発音しません。


8085等のALEピンがあるCPU出ない場合は、常時発音用クロック(19,20等)に接続しておくようにして、アドレス出力後に数μsほど(ALEに入力したクロックの周期以上)待機すると良いでしょう。


アドレスバスとデータバス共用したい場合は、ALEピンのクロック制御をマイコンで行います。
マイコン側で常に100kHz以上のクロックを生成してALEピンへ送ります。
どうしてもクロックの生成が厳しい場合は、データ書き込み後に4パルス以上ALEピンにクロックを与えてください。(要検証)
14 A3 I アドレスピンです。
15 A2I アドレスピンです。
16 A1 I アドレスピンです。
17 A0 I アドレスピンです。
18 ~RES I リセット端子です。

このピンがLの期間、リセットされます。
リセットを行うと、トーンレジスタ($0~$7)のピッチデータ(PD1~PD7)以外のレジスタビットがLになります。
19 CLK2 I グループ2(トーンジェネレータ4,5,6,7)の基準周波数です。

この端子に入力される周波数で発音周波数を決定します。
標準で1MHzの矩形波を入力します。
最大で2.5MHzの入力ができます。

SAS-20では、
    CLK1 : CLK2 = 255 : 256
のパルス比率となっています。
20 CLK1 I グループ1(トーンジェネレータ0,1,2,3)の基準周波数です。

この端子に入力される周波数で発音周波数を決定します。
標準で1MHzの矩形波を
入力します。
最大で2.5MHzの入力ができます。
21 VSS1(GND) - 電源端子です。
グランドです。
22 NO - ノイズ出力端子です。
レジスタ値に関係なく常にホワイトノイズを出力します。
23 VSS2 - 電源端子です。
-5Vの電源を用意してください。

単電源で使用する場合はGNDに接続します。
24 C0 I ーンジェネレータ0のエンベロープ用コンデンサ端子です。
25 C1 I トーンジェネレータ1のエンベロープ用コンデンサ端子です。
26 C2 I トーンジェネレータ2のエンベロープ用コンデンサ端子です。
27 C3 I トーンジェネレータ3のエンベロープ用コンデンサ端子です。
28 1-2' O グループ1(トーンジェネレータ0,1,2,3)の8倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
29 1-4' O グループ1(トーンジェネレータ0,1,2,3)の4倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
30 1-8' O グループ1(トーンジェネレータ0,1,2,3)の2倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
31 1-16' O グループ1(トーンジェネレータ0,1,2,3)の基倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
32 VSS3 - 電源端子です。
-5Vの電源を用意してください。

単電源で使用する場合はGNDに接続します。
33 2-16' O グループ2(トーンチャンネル4,5,6,7)の基音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
34 2-8' O グループ2(トーンジェネレータ4,5,6,7)の2倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
35 2-4' O グループ2(トーンジェネレータ4,5,6,7)の4倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
36 2-2' O グループ2(トーンジェネレータ4,5,6,7)の8倍音出力端子です。

電流加算で多チャンネルの合成行うので、外部に低インピーダンスな負荷を接続してください。
37 C4 I トーンジェネレータ4のエンベロープ用コンデンサ端子です。
38 C5 I トーンジェネレータ5のエンベロープ用コンデンサ端子です。
39 C6 I トーンジェネレータ6のエンベロープ用コンデンサ端子です。
40 C7 I トーンジェネレータ7のエンベロープ用コンデンサ端子です。
41 VDD2 - 電源端子です。
+5Vの電源を用意してください。
42 GATE O トーンジェネレータ7のソロ出力(solo 8' , solo 16')のゲートフラグの状態を出力します。





・MSM5232のレジスタ

アドレス
レジスタ名 機能
$0 tone 0 pitch トーン0の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF0PD0_6PD0_5PD0_4PD0_3PD0_2PD0_1PD0_0

出力する周波数fは、
   f = (CLK1 / 1M) * 440 * (2 ^ ((PD0 - 33) / 12))
   = (CLK1 / 15289) * (2 ^ (PD0 / 12))   [Hz]
   CLK1 : 
MSM5232の入力クロック周波数
   PD0 : このレジスタ値(PD0_0~PD0_6の7bit)
となります。

PD0の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD0 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF0 = H : キーオン
GF0 = L : キーオフ
$1
tone 1 pitch トーン1の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF1PD1_6PD1_5PD1_4PD1_3PD1_2PD1_1PD1_0

出力する周波数fは、
   f = (CLK1 / 1M) * 440 * (2 ^ ((PD1 - 33) / 12))
   = (CLK1 / 15289) * (2 ^ (PD1 / 12))   [Hz]
   CLK1 : 
MSM5232の入力クロック周波数
   PD1 : このレジスタ値(PD1_0~PD1_6の7bit)
となります。

PD1の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD1 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF1 = H : キーオン
GF1 = L : キーオフ
$2 tone 2 pitch
トーン2の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF2PD2_6PD2_5PD2_4PD2_3PD2_2PD2_1PD2_0

出力する周波数fは、
   f = (CLK1 / 1M) * 440 * (2 ^ ((PD2 - 33) / 12))
   = (CLK1 / 15289) * (2 ^ (PD2 / 12))   [Hz]
   CLK1 : 
MSM5232の入力クロック周波数
   PD2 : このレジスタ値(PD2_0~PD2_6の7bit)
となります。

PD2の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD2 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF2 = H : キーオン
GF2 = L : キーオフ
$3 tone 3 pitch トーン3の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF3PD3_6PD3_5PD3_4PD3_3PD3_2PD3_1PD3_0

出力する周波数fは、
   f = (CLK1 / 1M) * 440 * (2 ^ ((PD3 - 33) / 12))
   = (CLK1 / 15289) * (2 ^ (PD3 / 12))   [Hz]
   CLK1 : 
MSM5232の入力クロック周波数
   PD3 : このレジスタ値(PD3_0~PD3_6の7bit)
となります。

PD3の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD3 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF3 = H : キーオン
GF3 = L : キーオフ
$4 tone 4 pitch トーン4の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF4PD4_6PD4_5PD4_4PD4_3PD4_2PD4_1PD4_0

出力する周波数fは、
   f = (CLK2 / 1M) * 440 * (2 ^ ((PD4 - 33) / 12))
   = (CLK2 / 15289) * (2 ^ (PD4 / 12))   [Hz]
   CLK2 : 
MSM5232の入力クロック周波数
   PD4 : このレジスタ値(PD4_0~PD4_6の7bit)
となります。

PD4の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD4 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF4 = H : キーオン
GF4 = L : キーオフ
$5 tone 5 pitch トーン5の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF5PD5_6PD5_5PD5_4PD5_3PD5_2PD5_1PD5_0

出力する周波数fは、
   f = (CLK2 / 1M) * 440 * (2 ^ ((PD5 - 33) / 12))
   = (CLK2 / 15289) * (2 ^ (PD5 / 12))   [Hz]
   CLK2 : 
MSM5232の入力クロック周波数
   PD5 : このレジスタ値(PD5_0~PD5_6の7bit)
となります。

PD5の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD5 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF5 = H : キーオン
GF5 = L : キーオフ
$6 tone 6 pitch トーン6の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF6PD6_6PD6_5PD6_4PD6_3PD6_2PD6_1PD6_0

出力する周波数fは、
   f = (CLK2 / 1M) * 440 * (2 ^ ((PD6 - 33) / 12))
   = (CLK2 / 15289) * (2 ^ (PD6 / 12))   [Hz]
   CLK2 : 
MSM5232の入力クロック周波数
   PD6 : このレジスタ値(PD6_0~PD6_6の7bit)
となります。

PD6の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD6 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF6 = H : キーオン
GF6 = L : キーオフ
$7 tone 7 pitch トーン7の音程を決定します。
また、キーオン・キーオフを行うフラグビットもあります。


D7
D6
D5
D4
D3
D2
D1
D0
GF7PD7_6PD7_5PD7_4PD7_3PD7_2PD7_1PD7_0

出力する周波数fは、
   f = (CLK2 / 1M) * 440 * (2 ^ ((PD7 - 33) / 12))
   = (CLK2 / 15289) * (2 ^ (PD7 / 12))   [Hz]
   CLK2 : 
MSM5232の入力クロック周波数
   PD7 : このレジスタ値(PD7_0~PD7_6の7bit)
となります。

PD7の値は0~84(0x00~0x54)までピッチデータとしてセットできます。
PD7 = 127(0x7F)にセットすると、ノイズ音を出力します。
※0x55, 0x56, 0x58~0x7Eにセットしてもノイズ音が出ますが、非推奨です。


GF7 = H : キーオン
GF7 = L : キーオフ
$8 group 1 attack グループ1(トーンジェネレータ0,1,2,3)のエンベロープ(アタック)時間を設定します。


D7
D6
D5
D4
D3
D2
D1
D0
-----AT1_2AT1_1AT1_0


AT1(AT1_0~AT1_2の3bit)の値が大きいほど、アタック時間が増加します。
ただし、AT1_2がHのときAT1_1の値は無視されるため、実質6種類の中からアタック時間を選ぶことになります。

また、アタック時間はエンベロープ用コンデンサに依存します。
SAS-20ではエンベロープ用コンデンサは1μF固定ですが、POLY-800では外部エンベロープ制御をしています。
$9 group 2 attack グループ2(トーンジェネレータ4,5,6,7)のエンベロープ(アタック)時間を設定します。


D7
D6
D5
D4
D3
D2
D1
D0
-----AT2_2AT2_1AT2_0


AT2(AT2_0~AT2_2の3bit)の値が大きいほど、アタック時間が増加します。
ただし、AT2_2がHのときAT2_1の値は無視されるため、実質6種類の中からアタック時間を選ぶことになります。
$A group 1 decay グループ1(トーンジェネレータ0,1,2,3)のエンベロープ(ディケイ または リリース)時間を設定します。


D7
D6
D5
D4
D3
D2
D1
D0
----DT1_3DT1_2DT1_1DT1_0


DT1(DT1_0~DT1_2の3bit)の値が大きいほど、ディケイ時間が増加します。
DT1_2をHにすると、より長時間のディケイタイムを選択できます。

ただし、DT1_2がHのときDT1_1の値は無視されるため、実質12種類の中からディケイ時間を選ぶことになります。


グループ1のコントロールレジスタ($C)のARM1ビットがHの時、このレジスタ($A)はリリース値となります。(サスティンレベル=MAX、ディケイ=∞)

グループ1のコントロールレジスタ($C)のARM1ビットがLの時、このレジスタ($A)はディケイ値となります。(サスティンレベル=0、リリース兼用)


ディケイ時間はエンベロープ用コンデンサに依存します。
SAS-20ではエンベロープ用コンデンサは1μF固定ですが、POLY-800では外部エンベロープ制御をしています。
$B group 2 decay グループ2(トーンジェネレータ4,5,6,7)のエンベロープ(ディケイ または リリース)時間を設定します。


D7
D6
D5
D4
D3
D2
D1
D0
----DT2_3DT2_2DT2_1DT2_0


DT2(DT2_0~DT2_2の3bit)の値が大きいほど、ディケイ時間が増加します。
DT2_2をHにすると、より長時間のディケイタイムを選択できます。

ただし、DT2_2がHのときDT2_1の値は無視されるため、実質12種類の中からディケイ時間を選ぶことになります。


グループ2のコントロールレジスタ($D)のARM2ビットがHの時、このレジスタ($A)はリリース値となります。(サスティンレベル=MAX、ディケイ=∞)

グループ2のコントロールレジスタ($D)のARM2ビットがLの時、このレジスタ($A)はディケイ値となります。(サスティンレベル=0、リリース兼用)


ディケイ時間はエンベロープ用コンデンサに依存します。
SAS-20ではエンベロープ用コンデンサは1μF固定ですが、POLY-800では外部エンベロープ制御をしています。
$C group 1 control グループ1(トーンジェネレータ0,1,2,3)のコントロールレジスタです。
エンベロープの有無やエンベロープモード、各倍音の出力のON/OFFを決めます。


D7
D6
D5
D4
D3
D2
D1
D0
--EGE1ARM1OE1_2'OE1_4'OE1_8'OE1_16'


EGE1 = H : エンベロープを有効にします。
EGE1 = L : エンベロープを無効にします。エンベロープコンデンサの接続端子にアナログ電圧を印加することで音量制御します。


ARM1 = H : アタック・リリースモードになります。
ディケイ時間が∞でかつ、サスティンレベルがMAXとなり、
アタック時間とリリース時間は任意にセットできます。
管楽器やオルガンなど、キーオン中に音が鳴りっぱなしになるようなエンベロープ波形となります。

ARM1 = L : アタック・ディケイモードになります。
サスティンレベルが0となります。リリース時間はディケイレジスタ($A)と兼用です。
打楽器や打弦楽器など、キーオン中に音が減衰するようなエンベロープ波形となります。


OE1(OE1_2' ~ OE1_16')で各倍音の出力のON/OFFを選択します。
H : 出力
L : 非出力
$D group 2 control グループ2(トーンジェネレータ4,5,6,7)のコントロールレジスタです。
エンベロープの有無やエンベロープモード、各倍音の出力のON/OFFを決めます。


D7
D6
D5
D4
D3
D2
D1
D0
-SFEGE2ARM2OE2_2'OE2_4'OE2_8'OE2_16'


SF = H : ソロモード割り当て
SF = L : ソロモード非割り当て
ソロモードの割り当てを有効にすると、グループ2のトーン7のキーオン・キーオフ状態がGATE端子から出力され、倍音合成出力端子から出力しなくなります。
ノイズ音を利用したドラムなどに使用します。


EGE2 = H : エンベロープを有効にします。
EGE2 = L : エンベロープを無効にします。エンベロープコンデンサの接続端子にアナログ電圧を印加することで音量制御します。


ARM2 = H : アタック・リリースモードになります。
ディケイ時間が∞でかつ、サスティンレベルがMAXとなり、
アタック時間とリリース時間は任意にセットできます。
管楽器やオルガンなど、キーオン中に音が鳴りっぱなしになるようなエンベロープ波形となります。

ARM2 = L : アタック・ディケイモードになります。
サスティンレベルが0となります。リリース時間はディケイレジスタ($B)と兼用です。
打楽器や打弦楽器など、キーオン中に音が減衰するようなエンベロープ波形となります。


OE2(OE2_2' ~ OE2_16')で各倍音の出力のON/OFFを選択します。
H : 出力
L : 非出力


レジスタマップ
D7 D6 D5 D4 D3 D2 D1 D0
$0 GF0 PD0_6 PD0_5 PD0_4 PD0_3 PD0_2 PD0_1 PD0_0
$1 GF1 PD1_6 PD1_5 PD1_4 PD1_3 PD1_2 PD1_1 PD1_0
$2 GF2 PD2_6 PD2_5 PD2_4 PD2_3 PD2_2 PD2_1 PD2_0
$3 GF3 PD3_6 PD3_5 PD3_4 PD3_3 PD3_2 PD3_1 PD3_0
$4 GF4 PD4_6 PD4_5 PD4_4 PD4_3 PD4_2 PD4_1 PD4_0
$5 GF5 PD5_6 PD5_5 PD5_4 PD5_3 PD5_2 PD5_1 PD5_0
$6 GF6 PD6_6 PD6_5 PD6_4 PD6_3 PD6_2 PD6_1 PD6_0
$7 GF7 PD7_6 PD7_5 PD7_4 PD7_3 PD7_2 PD7_1 PD7_0
$8 AT1_2 AT1_1 AT1_0
$9 AT2_2 AT2_1 AT2_0
$A DT1_3 DT1_2 DT1_1 DT1_0
$B DT2_3 DT2_2 DT2_1 DT2_0
$C EGE1 ARM1 OE1_2' OE1_4' OE1_8' OE1_16'
$D SF EGE2 ARM2 OE2_2' OE2_4' OE2_8' OE2_16'
$E
$F



・タイミング図(書き込み)

-8085等CPUにALE出力ピンがある場合-




8085等のCPUは、パッケージ上の都合でアドレスバス(A0~A7)とデータバス(D0~D7)を共用しています。
そのため、アドレスかデータかの区別するために、ALE(アドレスラッチイネーブル)ピンをCPU側で用意しています。

MSM5232は、このALEピンがある組み込みプロセッサ向けのオルガン用音源ICです。
そのため、CPUが出力するALE信号をそのまま利用できます。

ALE信号は、CPUが逐次命令を実行する際にROMやRAM、周辺IOのアクセスに使用するため、MSM5232へのアクセスとは関係なしに常にHとLを繰り返しています。
MSM5232は、この信号の変化を内部ロジックのクロック源として利用しています。

よって、MSM5232にセットしたデータを反映させるには、ALEに4クロック以上のパルスを入力する必要があります。
8085等のCPUの場合、MSM5232へ連続書き込みを行わない限りALE信号を意識することは無さそうです。


ALEピンの扱い以外は、通常のIOアクセスと同じです。
ALEピンがHの間、A0~A3の値がMSM5232の内部レジスタへセットされます。
その後、~WEピンがLの間D0~D7の値がMSM5232の内部レジスタへセットされます。
この動作のあと、ALEピンに4クロック以上入力されることで、MSM5232の内部レジスタの内容が発音レジスタに反映されます。




-マイコンなどALE出力ピンがない場合-




「-8085等CPUにALE出力ピンがある場合-」の項で説明した通り、MSM5232のALEピンには、データの書き込み後にクロックを入力する必要があります。

ALEピンに入力するクロックは0.5μs~10μsでなければならないため、マイコンを用いてMSM5232を制御する場合は意図的にALE信号を生成する必要があります。

ただし、処理能力の低いマイコンや、何かの事情でクロックの生成が困難な場合は、オシレータなどで生成したクロックをMSM5232のALEピンに入力すると良いでしょう。

その場合、MSM5232の内部レジスタへアドレスをセットするタイミングが無数に発生します。
データ書き込み中にアドレス値が変更されてしまうのを防ぐため、データ書き込みフェーズが終了するまでA0~A3ピンには書き込み先アドレスを保持しておく必要があります。

規定上、データ書き込みフェーズ(~WEがLの期間)でALEがLの期間にデータ(D0~D7)の値をラッチさせるようですが、この手法の場合は不可能です。
もし、どうしても気になる方は、ALEクロック源と~WEのANDを取ったものをMSM5232のALEへ送ることで解決できそうです。(~WEがHの期間だけALEクロックをMSM5232へ送る)

わざわざANDロジックを入れなくても音は出ますが、問題が生じた場合には上記の様にANDロジックを入れる、または「-8085等CPUにALE出力ピンがある場合-」のような書き込み手順を行ってください。

書き込みフェーズ中でALEピンが次にHになるまでの時間は1μs以上でなければならないため、ALEピンに入力するクロックの周波数は100kHz~1MHzとする必要があります。


ALEピンに入力するクロック周波数が1MHzのときのデータの書き込み手順は、
① 書き込み先アドレスをA0~A3ピンにセットする。
② 1μs~2μs待機する。(ALEピンに入力されたクロックがHの期間にアドレスがMSM5232の内部レジスタにセットされる。)
③ データをD0~D7ピンへセットする。
④ ~WEピンをLにする。
⑤ ~WEピンをHにする。
⑥ 4μs以上待機する。



・使い方(制御)

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

・トーン設定レジスタ(8bit)
・エンベロープアタックレジスタ(3bit)
・エンベロープディケイレジスタ(4bit)
・コントロールレジスタ(6 or 7bit)


-トーン設定レジスタ(8bit)-
トーン設定レジスタは、各チャンネルに割り当てられており、$0~$7の合計8つあります。
このレジスタは、音程やキーオン・キーオフの制御で使用します。

レジスタの0~6bit目(PD)で音程を決定します。
この値が1増えると出力周波数が約1.05946倍(半音)になります。
音程としてセットできる値は、0~84(0x00~0x54)です。
87(0x57)をセットするとノイズ音が出ます。


レジスタの7bit目(GF)がキーオン・キーオフビット(ゲートフラグビット)です。
Hにするとキーオン、Lにするとキーオフします。


-エンベロープアタックレジスタ(3bit)-
エンベロープアタックレジスタは、グループごとに割り当てられており、$8,$9の2つあります。

レジスタの0~2bit目(AT)でアタック時間を決定します。
この値が大きいほど、アタック時間が増加します。
アタック時間は6種類の中から選べます。


-エンベロープディケイレジスタ(4bit)-
エンベロープディケイレジスタは、グループごとに割り当てられており、$A,$Bの2つあります。

レジスタの0~3bit目(DT)でディケイ時間を決定します。
0~2bit目の値が大きいほど、ディケイ時間が増加します。
3bit目をHにすると、より長時間のディケイタイムを選択できます。
アタック時間は12種類の中から選べます。


-コントロールレジスタ(6 or 7bit)-
コントロールレジスタは、グループごとに割り当てられており、$C,$Dの2つあります。

レジスタの0~3bit目(OE)で各倍音の出力のON/OFFを選択します。

4bit目(ARM)でエンベロープの種類を選択します。
ARM = H : アタック・リリースモード
ARM = L : アタック・ディケイモード

5bit目(EGE)がエンベロープ有効無効設定ビットです。
Hで有効、Lで無効になります。

6bit目(SF)がソロモード割り当て設定ビットです。(グループ2のみ存在)
SF = H : ソロモード割り当て
SF = L : ソロモード非割り当て



・MSM5232のセットアップ
1. 全てのレジスタに0を書き込む。(~RES端子をLにして10μs待機後Hにする。)
2. コントロールレジスタにエンベロープの有無、エンベロープの種類、倍音のON/OFFをセットする。
3. エンベロープアタックレジスタ、エンベロープディケイレジスタに任意の値をセットする。


・キーオンの手順
1. 発音したいチャンネルのトーン設定レジスタの0~6bit目に音程をセットする。
2. 発音したいチャンネルのトーン設定レジスタの7bit目のみHにする。


・キーオフの手順
発音したいチャンネルのトーン設定レジスタの7bit目のみLにする。
(0~6bit目の音程決定部分の変更はしないでください。)




・使い方(回路)

MSM5232は、基音、2倍、4倍、8倍の周波数の矩形波のみを出力します。
ただ単に合成しただけでは、PSGのような音しか出せませんが、この倍音の波形や合成比率を変化させてさらにアナログフィルタを接続することで、様々な音色を出すことができます。

合成比率と波形の形成は、MSM5232外部に接続するコンデンサ(波形の決定)と抵抗(合成比率)によって行います。



発音部分の基本の回路(KORG SAS-20の発音部分)


合成比率の調整は加算回路により行っています。
加算回路前段のコンデンサによって、低周波成分は波形が矩形波からひずむようになっています。
KORG SAS-20の場合、グループ1とグループ2の出力を共用して、倍音合成を行っているようです。
合成比率は、低音出力大モード(A)と等比モード(B)の2種類あります。


KORG POLY-800の場合、グループ1とグループ2の出力は別々に倍音合成を行っているようです。

Technics SX-K450では、倍音の合成前にそれぞれの倍音出力をフィルタに通しているようです。





・エンベロープ
エンベロープのアタック・ディケイ時間は、MSM5232の24~27、37~40番ピンに接続するコンデンサの容量によって決まります。
MSM5232のレジスタである程度コントロールできますが、レジスタでコントロールする場合では1つのグループ内で共通となるため、チャンネルごとにエンベロープ時間が設定できなくなります。

コンデンサの容量を変更することで、チャンネルごとのエンベロープ時間を変更できます。

KORG SAS-20の場合、すべてのチャンネルのエンベロープコンデンサが1μFとなっています。

KORG POLY-800の場合、ハードウェアエンベロープを無効(コントロールレジスタのEGEをL)にして、MSM5232のエンベロープコンデンサ接続端子にアナログ入力することで、エンベロープ&音量制御を行っているようです。(要検証)



・フィルタ
MSM5232の出力にアナログフィルタを接続すると、より豊かな音色を出すことができます。
各シンセサイザの音を再現するには、このフィルタ回路を実機と同一にする必要があります。


SAS-20のフィルタ回路


KORG SAS-20の場合、5種類の基本フィルタ回路が用意されています。
さらに上記回路図を見ると、フィルタ「B」、「C」では出力の抵抗が複数用意されています。
アナログマルチプレクサIC(4051)で8つのフィルタを適時選択して音色を変化させているようです。
実機では2SC945が使われていますが、現在このトランジスタは製造していないので代替品を用意する必要があります。ただし、トランジスタによって音が結構変わるので注意が必要です。


--KORG SAS-20の楽器と各レジスタ(フィルタや合成比率も含む)の関係--
実機で解析した結果です。ミスがあるかもしれません。
楽器 倍音合成
合成比率 フィルタ
2' 4' 8' 16' 4051のピン(回路図中の記号)
PIANO 等比(B) X3(B-3)
STRINGS 低音大(A) X6(D)
ORGAN 等比(B) X6(D)
FLUTE ○※ 等比(B) X3(B-3)
E.PIANO 等比(B) X2(B-2)
VIBRA-PHONE 等比(B) X2(B-2)
HARPSI-CHORD 等比(B) X7(E)
BRASS ENSEMBLE 低音大(A) X5(C-2)
JAZZ ORGAN 等比(B) X1(B-1)
CLARINET 低音大(A) X4(C-1)
JAZZ GUITAR
低音大(A) X0(A)
CLAV-SYNTHE 低音大(A) X7(E)
※キーオンから一定の時間経過すると発音が止まります。CPU側でMSM5232のレジスタを変更しているようです。





KORG POLY-800の場合、VCF用のICが内蔵されているため、より多様な音作りができます。


Technics SX-K450では、倍音の合成前にそれぞれの倍音出力をフィルタに通しているようです。フィルタ通過後にさらにVCFに通すことで味のあるサウンドが出せます。




・使用例  - MMLで演奏 -
MSM5232を2つ使用し、MMLで演奏するプログラム(Arduino)を紹介します。

・回路図
電源は+5Vのほかに-5Vも必要になります。(単電源で使用する場合は、MSM5232のVSS2とVSS3をGNDに落とします。ただし、オペアンプの電源が+5Vのみとなるため、オペアンプは低電圧品を使用し、アナログ部のGNDを2.5Vにする必要があります。)


この回路では、4種類の音色を出すことができます。音量は半固定抵抗を回すことでブロックごとに調整できます。 


基本的にMSM5232のノイズの発音は、トーン発音ブロックと共用しています。
よって、エンベロープやフィルタもトーン発音と共用になるため、発音の自由度が下がります。

そこで、今回の回路ではMSM5232のソロモードで利用することで、ノイズチャンネルを独立させています。
ソロモードの場合、エンベロープがないので、外部にエンベロープ回路を用意しています。


・プログラム


// MSM5232RSでMML演奏プログラム
//©oy
// https://oykenkyu.blogspot.com/2022/03/MSM5232RS.html

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


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

#include "avr/io.h"
#include "avr/interrupt.h"

#define SERIALSPEED 31250 // UARTのボーレート


#define CH 16  // MSM5232RS最大チャンネル数(MSM5232RSは1つで8チャンネル分発音できます。(ノイズチャンネル込み))
#define MML_MAX_TR CH // MMLトラック数


//MSM5232RSコントロールレジスタ
#define EGE_EN 0x20 // エンベロープ有効
#define ARM_H 0x10 // アタックリリースモードH(Hの時、キーオン中に音が持続)
#define SF_H 0x40 // ソロモード割り当て有効
#define OE_16 0x01 // 基音のみ出力
#define OE_8 0x02 // 2倍音のみ出力
#define OE_8_16 0x03 // 2倍音,基音を出力
#define OE_4 0x04 // 4倍音のみ出力
#define OE_4_16 0x05 // 4倍音,基音を出力
#define OE_4_8 0x06 // 4倍音,2倍音を出力
#define OE_4_8_16 0x07 // 4倍音,2倍音,基音を出力
#define OE_2 0x08 // 8倍音のみ出力
#define OE_2_16 0x09 // 8倍音,基音を出力
#define OE_2_8 0x0A // 8倍音,2倍音を出力
#define OE_2_8_16 0x0B // 8倍音,2倍音,基音を出力
#define OE_2_4 0x0C // 8倍音,4倍音を出力
#define OE_2_4_16 0x0D // 8倍音,4倍音,基音を出力
#define OE_2_4_8 0x0E // 8倍音,4倍音,2倍音を出力
#define OE_2_4_8_16 0x0F // 8倍音,4倍音,2倍音,基音を出力


// MSM5232RS_保存用
unsigned char msm5232rs_note[CH]; //ノートデータ(7bit)


//---------------演奏したいSMFに合わせて要変更--------------------
//デフォルトオクターブ[ch0,ch1,ch2,…,]
//増やすとオクターブが高くなります。(出力オクターブ = def_oct + (mmlのオクターブ))
char def_oct[CH] = {0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0};

//デフォルトキー
char def_key = -3;

//MMLチャンネルからMSM5232のチャンネルへ変換
//MSM5232のチャンネルの0~3はIC1ブロック1、 4~7はIC2ブロック2、8~11はIC2ブロック1、12~14はIC2ブロック2(15はノイズチャンネル)
char msm5232_tr[16] = {8, 13, 12, 0, 9, 1, 2, 3, 14, 15, 10, 7, 4, 5, 6, 11};
//回路例の場合
//IC1ブロック1 : PIANO
//IC1ブロック2 : STRINGS
//IC2ブロック1 : HARPSI-CHORD
//IC2ブロック2 : JAZZ GUITAR
//--------------------------------------------------------------

//ドラム発音用
unsigned char drum_note_num = 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;          //テンポ


//演奏のデータ
//簡易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)が使えます。
//

//HAPPY!!ストレンジフレンズ
const unsigned char PROGMEM mml_data[] =
"t106r1.r8o6a24r24c24d24r24f24r12f3r12c16r16d24r12f24r12e12f6>f24r24>a+24<f24r12c24r24>a+24r12<d48f48g24r24>a24r12<g24r12f8r24<d4..r16c16r16c24r12c4r1r1r1r1r1r1r1r1r1r1r1r8>>a+16a+16<c+4f4a+8.<f32c+32>a+8<c4..r16>e8f8g16r16<d16r8.f16r8.g16r16g16r16g16r4r16f8r2.c4d8c+16c16r8d16r1r1r16>g24r24a+24<c24r6r24f12r1r1r4r6f8e8f16r16c8..r1r1r2..r32f12r1r1r4r6f8e8f16r16c12r24c24r6r24c24r6r24c24r6r24c24r6r24c24r6r24c24r6r24c24r6r24c24r3r4f8g8a8a+8<c8r1.>a24r24c24d24r24f24r12f6f24r24c24r2.d4..r16c16r16c24r12c4r2.;r1r1r1r1r1o5a8.g16a8.g16a8.g16a8.g16a3r24a+16r16a+24<c12>a8g8f16r16a8.g16a8.g16a8.g16a8<d8c8.r8.>a8a+8a8g8f8r8a16r16f8r8f8g12a6f8r8a16r16f8.r16<c8>a+12a12r12f8r4f8f8f8g8a8f16r16f4g4a4a+8.r16f16r16f16r16f8r4a16r16g8f16r16c8f16r16f8r4f12f24g12g24f16r16f16r16f16r16f8r4a16r16g8f12r24c+4f4g8f8f8f8g2r8g8a8a8a+2r2f8.r16<c4>a32a+16.a8g8a8f8r12f24f8<d12c8r24>a12g8a6f16r16g8a8r12f8f24g8a8r8a+12a8a+8<c8>a6g12g6r8f8.r16<c4>a32a+16.a8g8a8f8r12f24f8<d12c8r24>a+12a8a+8<c3r12>f16r16f8g16a6r12r48a+8a+24a16r16f8r12a+8a+24a16r16f8r12a+8a+24a8f8d8f8g8a8a+8<c4..r8.>f12f24g12f24r12f24f8.r16<c4>a32a+16.a8g8a8f8r12f24f8<d12c8r24>a+12a8a+8<c4r6>f16r16f8g12a12r6a+8a+24a16r16f8r12a+8a+24a16r16f8r12a+8a+24a16r16f8r12a+8a+24a16r16f8r12a+8a+24a8f8d8f8g8a8a+8<c4..r8.>f12f24g12f24r12f12r3r4f12f24g12f24r12f12r1r1r4.r3;r2.o4f8>f8f8<c8f8c8e8a+8a12a24e8d8>e8f8a12a24<c8d+8f8a8a+8>f8<c12>g24a+12g24f8<f8e8f8>g8a8a+8b8<c8c12r24c4>a+8r4.a+12r24a+8r8<f16r16>a12r24a8r6r24a24<f12r24f8r12>f24a16.r32a+12r24a+8r4a+12r24a+8r8<c8d12r24d8r12>g24<c12d24f12r24f8d8c8>a+12r24a+8r4a+8<f12>a+6<f8>a12r24a8r8<f12f+24g+8<c8>g+8d+8>g8g12r8g24f12g24a8a12r8g24a8a+12r24a+16.r32<c12g24f8c12r24c12r24c12>g24<c8>a+12r24a+12r24a+8r2r8a12r24a12r24a8r4<f12a8g24c8>a+12r24a+12r24a+8r2f12g24a+2..r8<c4c4c2c12r6c12r6c12r24c32c+32f32g32<c12>g24c8>f8<c8f8g8e8a+8c+8>a8d8a8<d12r24c+8c8>g12f+24f8c8>a+8<d8f8a+8a8f8c12>g24a8g8a8a+8b8<c12r24c12r24c4f8a8<c8f8e8a+8a8e8d8>a8<d12r24c+8c8>f12<c8r24a12a+24a+8f8d12>a24a+8a8g8f8c8>g12r24g8a12g24a8a+12r24a+8<d12e24f8>g8<g8>a12g24a8a+12r24a+8b12r24b8<c8f12g24<c12>g24<d8c2>f8a8<c8d8e8a+8g8c+8d8e8f8<d8c8>f8g12a24f8a+8f8d12>a24a+8a8g8f8c8>g12r24g8a12g24a8a+12r24a+8<d12e24f8>g12r24g8a12g24a8a+12r24a+8<<d12>a+24f8>g8<g8>a12g24a8a+12r24a+8b12g24b8<c8f12g24<c12>g24<d8c2>f8<c8f8c8e8a+8>a12a24e8<f8g8a8<d8c8>a+8f24c6^c24>>g8a8a+8b8<c8c8c4f2.;r1o6c8c8c4d8r4.c6^c24>a24a8<d16r16d16r4r16>a8<d8d8d12>a+24a+12a24<c6^c24>g24r12f24r8>>g8a8a+8b8<c8c8c4<d8>f8a+8<c8f8g8a8a+8a8c8>a8<c8f8g8a8<c8>d8>f8a+8<c8f8g8a8a+8a8>a8<d8a8d8<e8c8.r16>d8>f8a+8<c8f8>f8a+8f8<c8>a8<f8>a8<d+8>g+8<<g12d+24c8>d8>g8<d8>g8<c8>a8<c8>a8<d8>f8a+8<d8c8c8c8r8<c16r16c16r16c8r4>f8g8a8<c16r16c16r16c8r4>a8g12f24c8<c16r16c16r16c8r4>f8g8a8r1r4>c4.<c8d8f8<c8r8c8r8c16r16c16r16c8r2r8d8r8c+8r2>>g+16f16d+8r8f4<<d8>a+8<d12>a+24a+16r16<c8g12a24a12f24c8>>>g8a8a+8b8<c8c8c4r2<<d4c+8<c+8d8>c8c16r8.d+8d16r16a8r4.>a+8.r16<c8>g16<c8r16c16r2.r16f8r1r4>>a+8a+8a+8<c2<c2d4c+8<c+8r8>c8c16r8.d+8d16r16a12r4r6>a+8.r16<c8>a+16<c8r16c16r16d16r16c16r16>a16r8.<d16r16c16r16f8r8d16r16c16r16>a16r8.<d16r16c16r16f8r1r4>>a+8a+8a+8<c2<c8c8c4d8r8d12r6c6^c24>a24a8<d16r16d16r8.e24r6r24>>>g8a8a+8b8<c8c8c4r2.;r1r1r1r1r1o7c24r12f24r12c24r12f24r12g24r12a24r12a24r12a+24r12a24r12c24r12g24r12>f24r12<g24r12d24r12g24r12f24r12c24r12f24r12>f24r12<c24r12c24r12g24r12a24r12a+24r12f24r12>a24r12<f24r12>a24r6r24a24r12<c24r12f24r12c24r12>a24r12a24r3<a24r12g24r12f24r6r24c24r12a24r12g24r12c24r12a24r12g24r48d+24r48c24r12f24r12d24r12a+24r12a24r12f24r12d24r12c24r12g24r12c24r12c24r12>a+24r3a+24r12<f24r24>a+24a+24r12<c24r12f24r12c24r4.r3c24r12f24r12c24r4.r3c24r12f24r12c24r4.r3f16r16c+16r16g+16r16f16r16f16r16c+16r16f16r16c+16r16c16r16g16r16c16r16<c16r16>c16r16g16r16e16r16g16r16d16r8.f16r8.g16r16g16r16g16r8.f24r12a24r12<c24r24c24e24r12>a+24r12<e24r12>g24r12<e24r12>f24r12<f24r24>f24f24r24<c24d24r24c24r8>a24r24g24r12a24r8f24r12<d24r24>g24d24r12<d24r12>f24r12<d24r24>g24c24r12<d24r12>d24r12a24r24d24d24r24<c24e24r24>g24a24r12a24r12a24r6r24a24r12a24r12<c24r24c24g24r12>a+24r12a24r12g24r12a24r12f24r12<f24r24>f24f24r12<d24r12c24r12>a+24r12a24r12c24r24<c24r8c24r12>f24r12f24r12f24r12g24r12a24r6r24f24r12<c24r12>c24r24f24<c24r12>f24r12<c24r12>c24r24f24<c24r12>c24r12a24r12<d24r12>f24r12<d+24r12c24r12d+24r12c24r12d24r12>f24r24f24<f24r12f24r12g24r3r8>a24r12a24r12<c24r24c24g24r12>a+24r12a24r12g24r12a24r12f24r12<f24r24>f24f24r12<d24r12c24r12>a+24r12a24r12c24r24<c24r8>f24r12<c24r12c24r12>f24r12g24r12a24r12<d24r24>f24f24r12<c24r12>c24r24f24<c24r12>f24r12<c24r12>c24r24f24<c24r12>f24r12<c24r12>c24r24f24<c24r12>f24r12<c24r12>c24r24f24<c24r12>c24r12a24r12<d24r12>f24r12<d+24r12c24r12d+24r12c24r12d24r12>f24r24f24<f24r12f24r12g24r1r1r1r1r6r24;r1o5a4.g8g8a4a8f8f12f24f8f12f24g4.r8f3r24f8g6^g24f6f8f8f8f8f8f16r16f24r12f8..r32>a+8<d8>a+2.<c2..c8>a+4a+2a+4<f4d4d4d4f8d4.r8f8>a+4<f8c8>a4<d+4d+4f8<d8>a+4f4.g8a8<c8>a+8<d8>g8g8g4f16r16f16r16f8r2r8f16r16f16r16f8r2r8f16r16f16r16f8r2r8c+2c+4.c+8f2^f8e8f8g8c16r8.f16r8.g16r16<c16r16>g8r8a4a4a+4g4a8f8a4g4a4a8f4f8f8g8<c8r8>a+4.b8<c4..r16>a4a4a+4a4a8.a4^a16g8g8a8a16a16<c8>a+8a+8a+8c4.c8g8g8f4f4a8g8f8g8f8a8f4f4f8f8f8f8g2a4a4g8g8a4a8.a4^a16g8g8a8a16a16<c8>a+8a+8a+8c4.c8g8g8a4f8f8a8g8g8g8a4f8f8a8g8f8g8f8a8f4f4f8f8f8f8g2a4.g8g8a4a8f8f12f24f8f12f24g8g8f4f8f8f8f8f16r16f24r12f4r2.;r2.o5f4f8f4.e8d8c+4d8c8c4f8d+8d+8f8d4.d8c2d8>a8a+8b8<c16r16c16r16c4f8>f2f4.a8<f8>a4.a8a4f2^f8f4.a8a4.a4a4a+4<a+8a+8a+8a+8a+4>a4<a4c4.c8r8g4.c4.c8d4f4f8f8f4>a+16r16a+16r16a+8r2r8a16r16a16r16a8r2r8a+16r16a+16r16a+8r2r8a+8a+4a+8a+4.a+8<c4c4.c4.>a+16r8.<c16r8.c16r16c16r16c8r8f8f8f4e8g8c+8e8d4f4d+4.f8f8d4.c8c8c8c8g8e4g8g8g8g8.r16f8.f8.f8g4g8e8f4f4f8d+8f8f8f8f4f4f8f8f8d4c4d4f4d4c8g8d8d8d4d8c8c8c8e8c4r8f8.f8.f8e8e8g8e8f4f4f8d+8f8f8f8f4f4f8f8f8d4c4d4f4d4c4d4f4d4c8g8d8d8d4d8c8c8c8e8c4.f8f4.e8d8c+4d8c8c4f8d+8c8>f8<d8>a8a+8b8<c16r16c16r16c4r2.;r2.o5c8>a+8<c8>a8<c4>a+4a4a4a4<c4c8c8>a+8a+8a+8f8f8a4.g8e8f8f+8g16r16g16r16g8..r32d1f1d1d8<c4.r8c8f4>f8f2f4.f4<c4>g+4g+4g8g4a+8a8a4a8a+2<c2>f16r16f16r16f8r2r8e16r16<c16r16c8r2r8>f16r16f16r16f8r2r8f8f4f2^f8g8g8g4g4g4g16r8.g16r8.g16r16g16r16g8r8<c4c4>a+4a4a8<c8d8c+8c8>a+8<c4d8>a+4a+8a4.a8g8a8a+8b8<c8c8c4c8c8c8c8>a+4a4a8.a4^a16<c8>a+8<c8>a8<d8d8d8>a+8a8g8a8a8g8<c8>a8a8a+8<c8>a+8<c8>g8a+8a8<c8>a+8a+8b8b8c8g8g8g8g2<c8c8c8c8>a+4a4a8.a4^a16<c8>a+8<c8>a8<d8d8d8>a+8a8g8a8a8g8<c8>a8a8a+8<c8>a+8<c8>g8<c8>a8a8a+8<c8>a+8<c8>g8a+8a8<c8>a+8a+8b8b8c8g8g8g8g4g4<c8>a8<c4>a+4a4a4a4<c8>g8f8a8g8e8f8f+8g16r16g16r16g4r2.;r1r1r1r1r1o6a8.g16a8.g16a8.g16a8.g16a3r24a+16r16a+24<c12>a8g8f16r16a8.g16a8.g16a8.g16a8<d8c8.r8.>a8a+8a8g8f8r8a16r16f8r8f8g12a6f8r8a16r16f8.r16<c8>a+12a12r12f8r4>d8d8c8c8c8c12r24d4>a+4<f4f6r12a12r24a12r24a8r4<c16r16>a+16.r32a16r16f8a16r16a8r4a12a24a+12a+24a16r16a12r24a12r24a8r4<c16r16>a+8a12r24f4g+4a+8g+8g+8g+8a+2r8a+8<c8c8d2r2>a8..r32<f8..r32d8c8>a+8<c8>a8r12a24a8<g12f8r24c12>a+8<c6>a16r16a+8<c8r12>a8a24a+8<c8r8d12c8d8f8d6c12c6r8>a8..r32<f8..r32d8c8>a+8<c8>a8r12a24a8<g12f8r24d12c8d8f3r12>a16r16a8a+16<c6r12r48d8d24c16r16>a8r12<d8d24c16r16>a8r12<d8d24c16r16>a8f8a8a+8<c8d8f4..r8.>f12f24g12f24r12f24a8..r32<f8..r32d8c8>a+8<c8>a8r12a24a8<g12f8r24d12c8d8f3r12>a16r16a8a+12<c8r8d8d24c16r16>a8r12<d8d24c16r16>a8r12<d8d24c16r16>a8r12<d8d24c16r16>a8r12<d8d24c16r16>a8f8a8a+8<c8d8f4..r8.>g12a24a+12a24r12a24r1r1r1..;r4.r3o3d48d48e24r24e24r12e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r6r24e24r24d24r8e24r24d24r8e24r6r24e24r24d24d24r4r6e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24e24r8e24r6r24e24r24e24r8e24r6r24e24r6r24e24r6r24e24r24d24e24r6d24r12d24r4e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r12e24r12e24r24d24r6r24d24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r6r24e24r48d32d32d24r24e24r12e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r48d32d32d24r6d24r12e24r12d24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24e24r24d48d48d24r24e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r6r24e24r24d24e24r24e24e24r24e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24e24r8e24r6r24e24r24e24e24r6r24d24r24d24d24r24d24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r6r24e24r24d24e24r24e24e24r24e24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24e24r8e24r6r24e24r24e24e24r6r24d24r24d24d24r24d24r8e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r6r24e24r24d24r8e24r6r24e24r24d24r6r24d24d24r24d24r12d24r2.;r1r1r16.o7e32f8e12d24>a8<c8>a16g16r12a8r24<c2r2>f4.r16a32a+32g32r16.g32r16.g8..r1r1r1r1r1r1r32<f24r12d24r12a+24r12a24r12g24r12f8e8f6r12c2^c6r6r24>f24r12<f24r12f24r2r3>f24r12f24r4.r3<f24r12>f24r2r3<f4g+4>a+4<c+4c8g8>g8r4<c4.>a+16r8.a+16r8.<g24r12g24r12g24r1r1r1r1r1.r12>f32r32f32r32f32r16.<a+24r6<f24d24r2.f24e6f12r24>a+24r24a+24a24r12f24r12c24r12f24r12c24r12f24r12c32d32e16f4f16>g8.a8a+8<c8d8<c16r1r1r1....>a+24r24a+24a24r12f24r12c24r12f24r12c24r12f24r12c32d32e16f24r12c24r12f24r12c24r12f24r12c24r12f24r12c32d32e16f4f16>g8.a8a+8<c8d8<c16r1r4..>>a+24r12a24r12a+24r12a24r12<c24r12c12c24d8e8f4g4f4.r8>g24r12g24r12g4a2.;r1o7f4<c4>a+8a8g8a8f8r1g8a4r2d16r16e16r16f16r16g16r16<c16r16c16r16c16r1r1r1r1r4r16>a8f8r8<c8>a8g8f8r8a8f8r1r1r1r1r1r1r1.r8d16r8.f16r8.<c16r16c16r16c16r8.>f4<c4>a+8a8g8a8f8r2..f16r16g8a8r8f16r16g8a8r1r8f4<c4>a+8a8g8a8f8r1...a+8a8f8r8a+8a8f8r8a+8a8f8d8f8g8a8a+8<c4r2.>f4<c4>a+8a8g8a8f8r1...a+8a8f8r8a+8a8f8r8a+8a8f8r8a+8a8f8r8a+8a8f8d8f8g8a8a+8<c4r2.>f4<c4>a+8a8g8a8f8r2..d16r16e16r16f16r16g16r16<c16r16c16r16c16r2...;r2.o5f24g24a24a+24<c24e24f4<c4>a+8a8g8a12f4.r24<d8c8>a12g24r12a6f8g12r24a8..r32<c4.>c12d48e48f4..r16<c24r12c24r12c6r1r1r1r1r1r1r12>f2..g16.r32<c2^c8>a+8a8a+8<c16r8.c16r2r8.c16r16c16r16c16r2r8.c16r16c16r16c16r2r8.>f2^f6r12c+4c1d8r8f8r8<c16r16c16r16c16r16>g24a24a+24<c2>a+8a8g8a8f2d12g24d8a4r8<c16r8.c16r8.c16r8.c8d16r16>e8f8g12<d24c16r16c16r16c16r48>c24e24g24a24<c2>g4a4f4.<d12c6>a+16f16a16a+16<c16>a+16d2f16r16f16r16e8f16r8.<c16r8.c16r8.c16r8.c16r16>f4g4a8a+8<c8d8f2g8r4>g24a24a+24<c2>g4a4f4.<d12c6>a+16f16a16a+16<c16>a+16d2f16r16f16r16e8f16r8.<c16r8.c16r8.c16r8.c16r8.c16r8.c16r8.c16r8.c16r16>f4g4a8a+8<c8d8f2g8r4.>f4<c4>a+8a8g8a12f4.r24<d8c4g4f4..r16c24r12c24r12c6r12>>>f2^f6r12;r1o7c4>a4r8d16r16d16r8r48<c3r12c8>g8<f16r16>a12<c8r24f8d12r24>f12r24f12r24a24r24a24f12g24r6r24g+48a+48<c4..r16>c24r12c24r12c12>a+12<c12r1r1r1r1r1r1d2c2f8r8g8r8f8r8f8r8f16r16f16r16f16r2r8.f16r16f16r16f16r2r8.f16r16f16r16f16r2r8.>f1g8c4>a+12<c2^c24a+12r24<d12r24d12r24d12r24f24r12f24r12d8e12g24a2g4a8e8a2g2a16r16g8a8f8g8g8a8f8d8c12d12r24f12r12a24r12g24r12g8r8a2a+8a8g8e8a6^a24a6a12g6c8f16g8e16f4g12a24f8g8.a16.r16.<c16r16>c8..r32>a4a+8r8<c4d4>g8<d8f12r24g12r24a12r24a+12r24<c2>e12r24c12r24c12r6a2a+8a8g8e8a6^a24a6a12g6c8f16g8e16f4g12a24f8g8.a16.r16.<c16r16>c8..r32>a4a+8r8<c4c8..r32>a4a+8r8<c4d4>g8<d8f12r24g12r24a12r24a+12r24<c2>e12r24c12r24c12r6<c4>a4r8d16r16d16r8r48<c3r12>d12r24d12r24g24r12e24r24g8r24<c4..r16>c24r12c24r12c4c2.;r2.o5c8>a+8<c8c8>f8<c8>>e8r16<e16>a8<e8d8a8d8>a8<c8d+8f4f4>a+4<f4e8f8>g8a8a+8b8<c16r16c16r16c8.r16>a+1a1a+1<d4..r16d8.r16d8.r16>a+1a2g+2g2a2a+2<c2>a+16r16a+16r16a+16r2r8.a16r16a16r16a16r2r8.a+16r16a+16r16a+16r2r8.a+1<c4c4c2c16r8.c16r8.c16r16c16r16c16r8.f8>f4.<e4>a4<d8a4.c2>a+2a2g8a8a+8b8<c2f8>f4.e4a4<d4.c+8c4f4>a+4<d4>a2g4a4a+4<d4>g4a8<a8>a+4b4<c2^c8c12r24c12r6f8>f4.e4a4<d2c2>a+4<d4>a2g4a4a+4<d4>g4a4a+4<d4>g4a4a+4b4<c1<c8c8>f8<c8>>e8r16<e16>a8<e8d8a8d4c4c4>g8a8a+8b8<c16r16c16r16c3a2^a6;";

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

//MSM5232RSへ書き込み
inline void msm5232rs_write(unsigned char cs, unsigned char adr, 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()
{
  // 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);
  //ポート設定
  // out_put
  DDRD = 0xFC;  // pin2~pin7_MSM5232RS:D2~D7
  PORTD = 0x00; // pin2~pin7_MSM5232RS:D2~D7
 
  DDRB = 0x3F;  // pin8~pin9_MSM5232RS:D0~D1   pin10~pin13_MSM5232RS:A0~A3
  PORTB = 0x00; // pin8~pin9_MSM5232RS:D0~D1   pin10~pin13_MSM5232RS:A0~A3

  DDRC = 0x0F;  // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
  PORTC = 0x00; // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
 

  // MSM5232RS発音用変数初期化
  for (int i = 0; i < CH; i++)
  {
    msm5232rs_note[i] = 0;
  }
  // MSM5232RSキーオフ
  for (int i = 0; i < CH; i++)
  {
    note_off(i);
  }

  //回路例の場合
  //IC1ブロック1 : PIANO
  //IC1ブロック2 : STRINGS
  //IC2ブロック1 : HARPSI-CHORD
  //IC2ブロック2 : JAZZ GUITAR
  // MSM5232RSエンベロープの設定初期化
  msm5232rs_write(0, 0x08, 0);//IC1グループ1アタック
  msm5232rs_write(0, 0x0A, 4);//IC1グループ1ディケイ
  msm5232rs_write(0, 0x0C, EGE_EN | OE_2_4_8);//IC1グループ1コントロール
  msm5232rs_write(0, 0x09, 5);//IC1グループ2アタック
  msm5232rs_write(0, 0x0B, 2);//IC1グループ2ディケイ
  msm5232rs_write(0, 0x0D, EGE_EN | ARM_H | OE_2_4_8);//IC1グループ2コントロール
   
  msm5232rs_write(1, 0x08, 1);//IC2グループ1アタック
  msm5232rs_write(1, 0x0A, 4);//IC2グループ1ディケイ
  msm5232rs_write(1, 0x0C, EGE_EN | OE_2_4_8_16);//IC2グループ1コントロール
  msm5232rs_write(1, 0x09, 0);//IC2グループ2アタック
  msm5232rs_write(1, 0x0B, 7);//IC2グループ2ディケイ
  msm5232rs_write(1, 0x0D, SF_H | EGE_EN | OE_4_8);//IC2グループ2コントロール
 
  // 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(20);
  }
}

//MSM5232RSへ書き込み
void msm5232rs_write(unsigned char cs, unsigned char adr, unsigned char data)
{
  //アドレスのセット
  delayMicroseconds(2);
  PORTB = (PORTB & 0xC3) | (0x3C & (adr << 2)); // pin9,8_MSM5232RS:A1,A0
  delayMicroseconds(4);//ALEにクロックが入力されるまで待つ
 
  //チップセレクト
  PORTC = (PORTC & 0xF0) | (0x0F); // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
  delayMicroseconds(2);

  //データのセット
  PORTB = (PORTB & 0xFC) | (0x03 & data); // pin9,8_MSM5232RS:D1,D0
  PORTD = (PORTD & 0x03) | (0xFC & data); // pin7~2_MSM5232RS:D7~D2

  //書き込み(~WE = L)
  delayMicroseconds(2);
  PORTC = (PORTC & 0xF0) | (0x0F & (~(1 << cs))); // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
  delayMicroseconds(2);
  PORTC = (PORTC & 0xF0) | (0x0F); // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
 
}

//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum)
{
  if (ch != 15)
  { //ドラムチャンネル以外
    while(notenum > 119){//(119 = 84 + 35)
      notenum -= 12;
    }
    while(notenum <= 35){
      notenum += 12;
    }
    msm5232rs_note[ch] = 0x7F & (notenum-35);
  }
  else
  { //ドラムチャンネル
    drum_note_num = notenum - def_key;
  }
}

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

//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel, unsigned char pan)
{
  // MSM5232RSは音量制御できません。
}

//チャンネルノートオン
void note_on(unsigned char ch)
{
  if (ch != 15)
  {
    //ドラムチャンネル以外
    msm5232rs_write(ch >> 3, 0x07 & ch, 0x80 | msm5232rs_note[ch]);
  }
  else
  {
    //ドラムチャンネル
    switch (drum_note_num)
    {
    case 32:
    case 21:
      break;
    case 25: //小
      break;
    case 27: //中
      break;
    case 33:
    case 35:
    case 36: //バス
      break;
    case 31: //スネア(ワイヤー)
      break;
    case 34://スネア
    case 38://スネア
    case 40: //スネア
      //ノートオフしてからノートオン
      msm5232rs_write(1, 0x07, 0x7F);//(127(0x7F)でノイズ音)
      msm5232rs_write(1, 0x07, 0x80 | 0x7F);//(127(0x7F)でノイズ音)
      break;
    case 49:            //シンバル
      break;
    case 57:            //シンバル
      break;
    case 42:            //クローズハイハット
      break;
    case 44:            //足ハイハット
      break;
    case 46:            //オープンハイハット
      break;
    case 127:           //リバースシンバル
      break;
    default:
      break;
    }
  }
}

//チャンネルノートオフ
void note_off(unsigned char ch)
{
  if (ch != 15)
  { //ドラムチャンネル以外
    msm5232rs_write(ch >> 3, 0x07 & ch, 0x7F & msm5232rs_note[ch]);
  }
  else
  {
    //ドラムチャンネル
  }
}

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

// 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(msm5232_tr[mml_st_tr], mml_com[1] +((mml_oct[mml_st_tr] + def_oct[mml_st_tr])*12) + def_key);//チャンネル, 音程
            note_on(msm5232_tr[mml_st_tr]);//ノートオン
          }
          mml_tie[mml_st_tr] = 0; //タイ無効
          break;
        case 'r': //ノートオフ
          note_off(msm5232_tr[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(msm5232_tr[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(msm5232_tr[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;
}
















・使用例  - MIDIの受信 -
MSM5232を2つ使用し、MIDIを受信する場合の回路とプログラム(Arduino)を紹介します。

・回路図





・プログラム

// MSM5232RSでMIDI演奏プログラム
//©oy
// https://oykenkyu.blogspot.com/2022/03/MSM5232RS.html


#include "avr/io.h"
#include "avr/interrupt.h"

#define SERIALSPEED 31250 // UARTのボーレート(MIDIのボーレートは31250bps)

#define CH 16  // MSM5232RS最大チャンネル数

//MIDI演奏最大トラック数(MSM5232RSは1つで8チャンネル分発音できます。(ノイズチャンネル込み))
#define MIDI_MAX_TR 16

//MSM5232RSコントロールレジスタ
#define EGE_EN 0x20 // エンベロープ有効
#define ARM_H 0x10 // アタックリリースモードH(Hの時、キーオン中に音が持続)
#define SF_H 0x40 // ソロモード割り当て有効
#define OE_16 0x01 // 基音のみ出力
#define OE_8 0x02 // 2倍音のみ出力
#define OE_8_16 0x03 // 2倍音,基音を出力
#define OE_4 0x04 // 4倍音のみ出力
#define OE_4_16 0x05 // 4倍音,基音を出力
#define OE_4_8 0x06 // 4倍音,2倍音を出力
#define OE_4_8_16 0x07 // 4倍音,2倍音,基音を出力
#define OE_2 0x08 // 8倍音のみ出力
#define OE_2_16 0x09 // 8倍音,基音を出力
#define OE_2_8 0x0A // 8倍音,2倍音を出力
#define OE_2_8_16 0x0B // 8倍音,2倍音,基音を出力
#define OE_2_4 0x0C // 8倍音,4倍音を出力
#define OE_2_4_16 0x0D // 8倍音,4倍音,基音を出力
#define OE_2_4_8 0x0E // 8倍音,4倍音,2倍音を出力
#define OE_2_4_8_16 0x0F // 8倍音,4倍音,2倍音,基音を出力


// MSM5232RS_保存用
unsigned char msm5232rs_note[CH]; //ノートデータ(7bit)


//---------------演奏したいSMFに合わせて要変更--------------------
//デフォルトオクターブ[ch0,ch1,ch2,…,]
//増やすとオクターブが高くなります。(出力オクターブ = def_oct + (mmlのオクターブ))
char def_oct[CH] = {0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0};

//デフォルトキー
char def_key = -3;

//MIDIチャンネルからMSM5232のチャンネルへ変換
//MSM5232のチャンネルの0~3はIC1ブロック1、 4~7はIC2ブロック2、8~11はIC2ブロック1、12~14はIC2ブロック2(15はノイズチャンネル)
char msm5232_tr[16] = {8, 13, 12, 0, 9, 1, 2, 3, 14, 15, 10, 7, 4, 5, 6, 11};
//回路例の場合
//IC1ブロック1 : PIANO
//IC1ブロック2 : STRINGS
//IC2ブロック1 : HARPSI-CHORD
//IC2ブロック2 : JAZZ GUITAR
//--------------------------------------------------------------

//ドラム発音用
unsigned char drum_note_num = 0; //ドラムチャンネルセット保存用

//MIDI受信用
char midi_main_vel[16];//メインベロシティ(MSM5232RSでは未使用)
char midi_pan[16];//パン(MSM5232RSでは未使用)
char midi_vel[16];//ベロシティ(MSM5232RSでは未使用)

unsigned char midi_buf[256]; //受信バッファ
int ex_mess_en=0;            //midi_read()で使用
int dat_ph=0;                //midi_read()で使用
unsigned char read_buf_h;    //midi_read()で使用
int stop_byte=0;             //midi_read()で使用
unsigned char read_buf=0;    //midi_read()で使用
unsigned char running_ch=0x90;//ランニングステータスチャンネル

unsigned char midiprog[16];    //MIDIプログラムチェンジ保存
unsigned char sas_en = 0;      //サスティン有効フラグ


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

//MSM5232RSへ書き込み
inline void msm5232rs_write(unsigned char cs, unsigned char adr, 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);

//MIDI受信関係
//1バイトずつmidi_readを実行し、1グループのmidiメッセージを受信し終えるとmidi_comを実行
void midi_read(char read_buf);
//MIDIデータ入力処理
inline void midi_com(unsigned char *in_midi_mess);


//セットアップ
void setup()
{
  //デバッグメッセージ用
  Serial.begin(SERIALSPEED); //シリアル通信開始(MIDI受信)
  //ポート設定
  // out_put
  DDRD = 0xFC;  // pin2~pin7_MSM5232RS:D2~D7
  PORTD = 0x00; // pin2~pin7_MSM5232RS:D2~D7
 
  DDRB = 0x3F;  // pin8~pin9_MSM5232RS:D0~D1   pin10~pin13_MSM5232RS:A0~A3
  PORTB = 0x00; // pin8~pin9_MSM5232RS:D0~D1   pin10~pin13_MSM5232RS:A0~A3

  DDRC = 0x0F;  // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
  PORTC = 0x00; // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
 

  // MSM5232RS発音用変数初期化
  for (int i = 0; i < CH; i++)
  {
    msm5232rs_note[i] = 0;
  }
  // MSM5232RSキーオフ
  for (int i = 0; i < CH; i++)
  {
    note_off(i);
  }

  //回路例の場合
  //IC1ブロック1 : PIANO
  //IC1ブロック2 : STRINGS
  //IC2ブロック1 : HARPSI-CHORD
  //IC2ブロック2 : JAZZ GUITAR
  // MSM5232RSエンベロープの設定初期化
  msm5232rs_write(0, 0x08, 0);//IC1グループ1アタック
  msm5232rs_write(0, 0x0A, 4);//IC1グループ1ディケイ
  msm5232rs_write(0, 0x0C, EGE_EN | OE_2_4_8);//IC1グループ1コントロール
  msm5232rs_write(0, 0x09, 5);//IC1グループ2アタック
  msm5232rs_write(0, 0x0B, 2);//IC1グループ2ディケイ
  msm5232rs_write(0, 0x0D, EGE_EN | ARM_H | OE_2_4_8);//IC1グループ2コントロール
   
  msm5232rs_write(1, 0x08, 1);//IC2グループ1アタック
  msm5232rs_write(1, 0x0A, 4);//IC2グループ1ディケイ
  msm5232rs_write(1, 0x0C, EGE_EN | OE_2_4_8_16);//IC2グループ1コントロール
  msm5232rs_write(1, 0x09, 0);//IC2グループ2アタック
  msm5232rs_write(1, 0x0B, 7);//IC2グループ2ディケイ
  msm5232rs_write(1, 0x0D, SF_H | EGE_EN | OE_4_8);//IC2グループ2コントロール
 
}

void loop()
{
  while (1)
  {
    if (Serial.available() > 0)
    {
      midi_read(Serial.read());//イベント処理
    }
  }
}

//MSM5232RSへ書き込み
void msm5232rs_write(unsigned char cs, unsigned char adr, unsigned char data)
{
  //アドレスのセット
  delayMicroseconds(2);
  PORTB = (PORTB & 0xC3) | (0x3C & (adr << 2)); // pin9,8_MSM5232RS:A1,A0
  delayMicroseconds(4);//ALEにクロックが入力されるまで待つ
 
  //チップセレクト
  PORTC = (PORTC & 0xF0) | (0x0F); // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
  delayMicroseconds(2);

  //データのセット
  PORTB = (PORTB & 0xFC) | (0x03 & data); // pin9,8_MSM5232RS:D1,D0
  PORTD = (PORTD & 0x03) | (0xFC & data); // pin7~2_MSM5232RS:D7~D2

  //書き込み(~WE = L)
  delayMicroseconds(2);
  PORTC = (PORTC & 0xF0) | (0x0F & (~(1 << cs))); // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
  delayMicroseconds(2);
  PORTC = (PORTC & 0xF0) | (0x0F); // pin14~pin17_MSM5232RS:~WE_0~ ~WE_3
 
}

//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum)
{
  if (ch != 15)
  { //ドラムチャンネル以外
    while(notenum > 119){//(119 = 84 + 35)
      notenum -= 12;
    }
    while(notenum <= 35){
      notenum += 12;
    }
    msm5232rs_note[ch] = 0x7F & (notenum-35);
  }
  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)
{
  // MSM5232RSは音量制御できません。
}

//チャンネルノートオン
void note_on(unsigned char ch)
{
  if (ch != 15)
  {
    //ドラムチャンネル以外
    msm5232rs_write(ch >> 3, 0x07 & ch, 0x80 | msm5232rs_note[ch]);
  }
  else
  {
    //ドラムチャンネル
    switch (drum_note_num)
    {
    case 32:
    case 21:
      break;
    case 25: //小
      break;
    case 27: //中
      break;
    case 33:
    case 35:
    case 36: //バス
      break;
    case 31: //スネア(ワイヤー)
      break;
    case 34://スネア
    case 38://スネア
    case 40: //スネア
      //ノートオフしてからノートオン
      msm5232rs_write(1, 0x07, 0x7F);//(127(0x7F)でノイズ音)
      msm5232rs_write(1, 0x07, 0x80 | 0x7F);//(127(0x7F)でノイズ音)
      break;
    case 49:            //シンバル
      break;
    case 57:            //シンバル
      break;
    case 42:            //クローズハイハット
      break;
    case 44:            //足ハイハット
      break;
    case 46:            //オープンハイハット
      break;
    case 127:           //リバースシンバル
      break;
    default:
      break;
    }
  }
}

//チャンネルノートオフ
void note_off(unsigned char ch)
{
  if (ch != 15)
  { //ドラムチャンネル以外
    msm5232rs_write(ch >> 3, 0x07 & ch, 0x7F & msm5232rs_note[ch]);
  }
  else
  {
    //ドラムチャンネル
  }
}

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



//MIDI受信用関数/////////////////////////////
//1バイトずつmidi_readを実行し、1グループのmidiメッセージを受信し終えるとmidi_comを実行
void midi_read(char read_buf)
{
  if (ex_mess_en == 1)
  {
    if (read_buf == 0xf7)
    { //エクスクルーシブ・メッセージ終了
      ex_mess_en = 0;
      dat_ph = stop_byte;
    }
  }
  else if (((read_buf >> 7) & 0x01) == 1)
  {
    //データ始まり検出したら
    dat_ph = 0;
    ex_mess_en = 0;
    read_buf_h = 0xF0 & read_buf;
   
    if ((0x80 <= read_buf_h) && (0xB0 >= read_buf_h) || (read_buf_h == 0xE0))
    {
      //3バイト読み取り
      running_ch = read_buf; //ランニングステータスチャンネルセット
      stop_byte = 2;
    }
    else if ((read_buf_h == 0xC0) || (read_buf_h == 0xD0))
    {
      //2バイト読み取り
      running_ch = read_buf;
      stop_byte = 1;
    }
    else
    {
      switch (read_buf)
      {
        case 0xF0: //エクスクルーシブ・メッセージ
        ex_mess_en = 1;
        stop_byte = 255;
        break;
        case 0xF1: //MIDIタイムコード
        case 0xF3: //ソング・セレクト
        stop_byte = 1;
        break;
        case 0xF2: //ソング・ポジション・ポインター
        stop_byte = 2;
        break;
        case 0xF6: //チューン・リクエスト
        case 0xF7: //エンド・オブ・エクスクルーシブ
        case 0xF8: //タイミング・クロック(MIDIクロック)
        case 0xFA: //スタート
        case 0xFB: //コンティニュー
        case 0xFC: //ストップ
        case 0xFF: //システム・リセット
        stop_byte = 0;
        break;
        case 0xFE: //アクティブ・センシングc
        stop_byte = 0;
        break;
      }
    }
  }
  else if (dat_ph == 0)
  {
    //ランニングステータス//////
    stop_byte = 2;
    midi_buf[0] = running_ch;
    dat_ph++;
    //////////////////////////
  }
  midi_buf[dat_ph] = read_buf; //受信データ格納
  if (dat_ph >= stop_byte)
  {
    //指定バイト数受信完了
 
    stop_byte = 3;
    ex_mess_en = 0;
    if((midi_buf[0]==0xFE) && (dat_ph == 0))
    {
      dat_ph = 0;//アクティブセンシングは処理しない
    }
    else
    {
      midi_com(midi_buf);
    }
   
    dat_ph = 0;
    midi_buf[0] = 0;
    return;
  }
  if ((((midi_buf[0] >> 7) & 0x01) == 1) || (ex_mess_en == 1))
  {
    //1バイト目がメッセージ以外読み取りしない
    dat_ph++;
  }
}
//MIDIデータ入力処理
inline void midi_com(unsigned char *in_midi_mess)
{
  unsigned char in_midi_data_buf_h0 = in_midi_mess[0] >> 4;   //上位4ビットのみを右へ4ビットシフト
  unsigned char in_midi_data_buf_l0 = in_midi_mess[0] & 0x0f; //下位4ビットのみ チャンネル
  unsigned char in_midi_data_buf_1 = in_midi_mess[1];
  unsigned char in_midi_data_buf_2 = in_midi_mess[2];

  //ドラムパート
  if (in_midi_data_buf_l0 == 0x09)
  {
    drum_note_num=in_midi_mess[1];
    if (in_midi_data_buf_h0 == 0x08){
      //ノートオフ
      note_off(0x0F);
    }
    else if (in_midi_data_buf_h0 == 0x09){
      if (in_midi_mess[2] == 0){
        //ノートオフ
        note_off(0x0F);
      }
      else{
        note_on(0x0F);//ノートオン
      }
    }
    return;
  }
  //80
  if (in_midi_data_buf_h0 == 0x08)
  {
    //ノートオフ
    if(in_midi_data_buf_l0 < MIDI_MAX_TR){
      note_off(msm5232_tr[in_midi_data_buf_l0]);
    }
  }
  //90
  else if (in_midi_data_buf_h0 == 0x09)
  {
    if (in_midi_mess[2] == 0)
    {
      //ノートオフ
      if(in_midi_data_buf_l0 < MIDI_MAX_TR){
        note_off(msm5232_tr[in_midi_data_buf_l0]);
      }
    }
    else
    {
      midi_vel[in_midi_data_buf_l0] = in_midi_data_buf_2;//ベロシティ保存
      //ノートオン
      if(in_midi_data_buf_l0 < MIDI_MAX_TR){
        //note_off(msm5232_tr[in_midi_data_buf_l0]);
        ptc_set(msm5232_tr[in_midi_data_buf_l0], in_midi_mess[1] +(def_oct[in_midi_data_buf_l0]*12) + def_key);//チャンネル, 音程
        vel_pan_set(msm5232_tr[in_midi_data_buf_l0], ((int)((int)midi_vel[in_midi_data_buf_l0] * (int)midi_main_vel[in_midi_data_buf_l0] )>>7), midi_pan[in_midi_data_buf_l0]);//チャンネル, 音量, パン
        note_on(msm5232_tr[in_midi_data_buf_l0]);//ノートオン
      }
    }
  }
  else if (in_midi_data_buf_h0 == 0x0A)
  {
    //ポリフォニックキープレッシャー
    //何もしない
  }
  else if (in_midi_data_buf_h0 == 0x0B)
  {
    //コントロールチェンジ
    switch (in_midi_mess[1])
    {
    case 0x07: //チャンネルボリューム
      midi_main_vel[in_midi_data_buf_l0] = in_midi_mess[2];
      break;
    case 0x0A: //パン
      midi_pan[in_midi_data_buf_l0] = in_midi_mess[2];
      break;
    case 0x78:
    case 0x79:
      for (int i = 0; i < MIDI_MAX_TR; i++)
      {
        note_off(i);
      }
      break;
    case 0x40: //サスティン
      if (in_midi_mess[2] == 0x7F)
      {
        sas_en = 1;
      }
      else
      {
        sas_en = 0;
      }
      break;
    default:
      break;
    }
  }
  else if (in_midi_data_buf_h0 == 0x0C)
  {
    //プログラムチェンジ
    midiprog[in_midi_data_buf_l0]=in_midi_mess[1];
  }
  else if (in_midi_data_buf_h0 == 0x0E)
  {
    //ピッチベンド
    //何もしない
  }
  else if (in_midi_data_buf_h0 == 0x0F)
  {
    //システムメッセージ
    if (in_midi_mess[0] == 0xFE)
    {
      //アクティブセンシング
    }
    if (in_midi_mess[0] == 0x00)
    {
      //エクスクルーシブ・メッセージ
      if (in_midi_mess[1] == 0x7E && in_midi_mess[2] == 0x7F)
      {
        //ユニバーサル
        if (in_midi_mess[3] == 0x09)
        {
          //GM
          if (in_midi_mess[4] == 0x01)
          {
            //GMシステム・オン
            for (int i = 0; i < MIDI_MAX_TR; i++)
            {
              note_off(i);
            }
          }
          if (in_midi_mess[4] == 0x02)
          {
            //GMシステム・オフ
            for (int i = 0; i < MIDI_MAX_TR; i++)
            {
              note_off(i);
            }
          }
        }
      }
    }
  }
}










 







0 件のコメント:

コメントを投稿