*注意*

近年、AIによるサイト内情報の要約や解説など便利な機能が増えています。 ですが、技術系のブログについてのAI要約はまだまだ不正確な記述をする場合が多いです。 AI要約や解説を鵜呑みにせず、元記事を参照して必ず自らの目で確かめて判断するようにしてください。 *Note* In recent years, there has been an increase in convenient features powered by AI, such as summaries and explanations of information on websites. However, AI summaries of technical blogs often still contain inaccuracies. Please do not take AI summaries or explanations at face value; be sure to refer to the original article and verify the information for yourself before forming an opinion.

AI利用者への案内

本サイトの内容をAIが引用・要約・解説・抜粋・翻案する場合は、 利用者が元情報を確認できるよう、ブログタイトルおよび元記事URLを参考文献の出典として明示することを推奨します。 Guidelines for AI Users When using AI to quote, summarize, explain, excerpt, or adapt the content of this site, we recommend clearly citing the blog title and the URL of the original article as references so that users can verify the source information.

2019年6月23日日曜日

KY-3201P SGC PSG音源ICの使い方


KY-3201P SGC PSG音源ICの使い方


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

秋月電子のお楽しみ袋に入っていた謎のIC 「TAXAN KY-3201P SGC」を0から解析してみました。
その結果、4音+ノイズ1音のSSG音源ICであることがわかりました。
この記事では、解析から演奏までを説明します。「1.解析」は飛ばしても構いません。
現在も解析中であるため、内容はちょくちょく修正されます。ご了承ください。また、間違いや新しい情報などコメント欄にコメントして頂くととても助かります。


構成:
1.解析
2.仕様
3.使い方
4.テスト
5.プログラム
6.まとめ


1.解析

ネット上にはデータシートはおろか、採用例なども見つからず使用を諦めていました。

ですが、このICがPSG音源ICという情報を見つけ解析を始めました。
まず、データーシートが手に入らない以上ICのピンがどのようになっているかが不明です。なので手掛かりを見つけるためにICを燃やしてダイの構造を観察することにしました。

ダイの構造を見ると左下らへんにレジスタ領域があります。(Twitter上ではまだこのICが何者かわからなかったためにROM書いてありますが間違いです。)
拡大してみると8bit×16が2つあり、アドレスが0x00から0x1Fをもつ8bitレジスタ群ということが予想出来ます。制御する上で重要な情報が入りました。

次にICのピンとダイを繋ぐボンディングパッドの形状を見てみます。
上下4か所、左右6か所あり、下の2か所が特徴的な形状をしています。
他の箇所と比べ大きくなっているので電源ピンと仮定することが出来ます。
また、その間にあるボンディングパッドは他の箇所と比べ小さいく、1か所しかないため特別な入出力ピンと予想できます。
このICのピンで言うと電源ピンは4番と6番に当たることがわかります。ここでピンと来る方もいるでしょう。
そう、このICの電源ピンの位置とYAMAHAのPSG音源IC「YMZ294」の電源ピンの位置が似ています。つまりこのIC自体「YMZ294」に近い性能を有している可能性があります。
以上がICのダイからわかる情報です。

ここからはこの予想をもとに各ピンの役割を解明していきます。
「YMZ294」に近いICということがわかったので、「YMZ294」のピンアサインと近いことが予想できます。そこで「YMZ294」のデータシートをもとに解析していきます。
まずは電源ピンの確定です。ダイの画像からは電源ピンの位置はわかりますが、VddかGNDかがどちらかわかりません「YMZ294」のデータシートを参考にすると4番ピンがVdd、6番ピンがGNDとなっています。なのでとりあえず、この通りに接続してみたところICに過大な電流が流れてしまいました。そこで今度は4番ピンがGND、6番ピンVddとして接続したところ過大な電流は流れず、電源ピンの位置が確定しました。

次に音の出力ピンの位置を予想します。「YMZ294」のデータシートを参考にすると電源ピンの間の5番ピンが音の出力であることがわかります。ダイの画像を見ると他の箇所のボンディングパッドと大きさが違うことから、このICの音の出力ピンは5番ピンであることが暫定できます。

これで3/20ピンの役割がわかりました。次に「YMZ294」のデータシートをもとに、システムクロック(ICの音生成用クロック)、データーバス、CS(チップセレクト)、WE(ライトイネーブル)、A0(データ・アドレス選択)、IC(リセット入力)ピンの位置を探ります。ダイ画像ではこの項目の判別が完全につかないため「YMZ294」のデータシートを参考にマイコンから演奏データを送りながら音の鳴る条件を探ります。
始めに「YMZ294」の電源ピン以外の対応するピンをこのICのピンとマイコンを接続してみました。ですが音が出ませんでした。よくよく考えてみると「YMZ294」とこのICの電源ピンの位置も若干違うため、これらの入力ピンも違う可能性があることに気付きました。
そこで根気強くこれらのピンを総当たり戦法でマイコンと接続しました。そこで作業開始から数時間後のこと、特定の入力パターンでデジタル出力になることがわかりました。
デジタル出力となるピンは7~11番ピンの5個と13~20番ピンの8個です。また、7~11番ピンは不明な条件によって出力パターンが変わることも判りました。
ここで13~20番ピンが8個ということでデーターバスであることが予想できます。
また、7~11番ピンは5個、つまり5bitのデータの送受信と考えるとダイ画像から予想した
アドレス0x1fをbitに直すと5bitであるため、7~11番ピンはアドレスバスであると暫定できます。
この結果は「YMZ294」のデータシートからの予想よりだいぶかけ離れていました。

これで残りは1、2、3、12ピンの4つです。
A0(データ・アドレス選択)ピンは不要なので除外し、残るはシステムクロック(ICの音生成用クロック)、CS(チップセレクト)、WE(ライトイネーブル)、IC(リセット入力)です。
「YMZ294」のデータシートは参考にならないと判り、総当たり戦法で見つけていきます。その結果、1番ピンはシステムクロック(ICの音生成用クロック)、2番ピンがIC(リセット入力)の可能性、3番がOE/WE(アウトイネーブル/ライトイネーブル選択)またはTEST入力端子の可能性、12番ピンがWEまたはCSの可能性が判りました。ただし、2、3、12番ピンの正確な役割はわかりません。

以上でこのICのピンの役割の解析が終わりました。
この通りにマイコン接続し、アドレス0x00~0x1Fにデータ0x00~0xFFを順番に書き込んだところランダムな音が出力されました。

ここからはレジスタマップの解析をしてきます。とはいうものの、アドレス0x00~0x1Fにデータ0x00~0xFFに書き込んでどのような音が出るか調べるので難易度は低いです。
不明な部分も多いですがとりあえず音を出すことは出来ました。
2の使い方にICのピンとアドレスマップをまとめます。

あと、「KY-3201P SGC」のSGCはサウンドジェネレーターコントローラーの略ではないかと思います。

2.仕様

解析した「KY-3201P SGC」仕様です。もちろんオリジナルのデータシートはありません。
現在も解析中であるため、内容はちょくちょく修正されます。2019年6月日現在の情報です。
秋月電子で手に入るPSG音源IC「YMZ294」のデータシートを模倣して作成しました。

■特徴
・独立した4チャンネルの矩形波+ノイズ1音の音源
・各チャンネルは設定した音程の1倍、2倍、4倍、8倍の矩形波を合成した波形を出力可能
・CS制御信号と8ビットデータバスと5ビットアドレスバスによるCPUインターフェース
・2ビット分周レジスタにより幅広い周波数のマスタークロックを利用可能

■YMZ294との比較
・VDD端子とGND端子の位置が逆(重要)
・矩形波生成ユニットがYMZ294より1つ多い
・データバスとアドレスバスが独立している
・YMZ294では単純な矩形波のみに対し、KY-3201P SGCでは、設定した音程の1倍、2倍、4倍、8倍の矩形波を合成した波形を出力可能



■端子配線図
「?」が付いている端子はまだ仕様がはっきりと判っていません。

■予想端子機能

■端子機能説明

1.ΦM
   マスタークロック入力です。入力周波数は4MHzから32MHzです。
   ※クリスタルオシレーターを変更して確かめたところ、60MHzのものでも一応動作しました。(音程が高すぎるため実用不可)
2.A0~A4
   5ビットのアドレスバスです。
3.D0~D7
   8ビットのデータバスです。
4./CS,W/R
   5ビットのアドレスバスのアドレスに8ビットのデータバスのデータを書き込みをコントロールします。
   W/Rが'H'、/CSがLの時データを書き込みます。
   ※とりあえずW/Rを'H'に固定して制御します。
5./IC
   'L'の時システムリセットになります。
   ※リセット時レジスタアレーの内容が全ての'0'になるのか未検証です。
6.SO
  音声信号のアナログ出力です。
7.VDD
   +5Vの電源端子です。
8.GND
   接地端子です。

■タイミング図


■レジスタマップ
注) レジスタマップの内容はまだはっきりと判っていないため頻繁に変わります。
※ここで緑色は「ほぼ仕様が確定している」
   黄色は「そうではないか?」
   赤色は「完全に不明だけど変更すると音の変化がみられる」
   白色に(X)は「現時点では変更しても変化がみられない」
   状態を表しています。

■レジスタ機能説明
※仕様がほぼ確定しているレジスタのみ説明します。

1.楽音周波数の設定
   4チャンネル(A、B、C、D)の楽音発生器で作られる周波数ftは次のように決められます。

   ft=fΦM /(2^DI)×32×TP

   TP=(TP11×2^11)+(TP10×2^10)+(TP9×2^9)+(TP8×2^8)+
   (TP7×2^7)+(TP6×2^6)+(TP5×2^5)+(TP4×2^4)+
   (TP3×2^3+(TP2×2^2)+(TP1×2^1)+(TP0)

   DI=(DI1×2^1)+(DI0)

   [ft:楽音周波数]
   [fΦM:「ΦM」の入力周波数]
   [TP0~TP11:楽音周波数設定レジスタ($00,01、$05,06、$0A,0B、$0F,10)]
   [DI0、DI1:システムクロック分周レジスタ($1F)]

   また、各チャンネルはftをもとに1倍、2倍、4倍、8倍の周波数を合成して出力できます。(波形は論理ORで出力されます。)
   オクターブ合成レジスタを利用すると音色やDuty比を変化できます。

   fso=(H3×2^3×ft)&(H2×2^2×ft)&(H1×2^1×ft)&(H0×ft)

   [fso:各チャンネル合成出力周波数]
   [H3~H0:オクターブ合成レジスタ($03、$08、$0D、$12)]

例:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
クリスタルオシレーター8MHz
分周レジスタDI=1
楽音周波数設定レジスタTP=000100011100=284
オクターブ合成レジスタH0=1、H1=1、H2=0、H3=0の時

ft=8×10^6 /(2^1)×32×284=440.14Hz

fso=(0×2^3×ft)&(0×2^2×ft)&(1×2^1×ft)&(1×ft)=880.28Hz | 440.14Hz

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

3.使い方

ここでは簡単に音を鳴らす方法を紹介します。

音を出力するには、
・音量レジスタを1以上にする($02、$07、$0C、$11)
   ※($02、$07、$0C、$11)のD0とD1が'0'の場合
・オクターブ合成レジスタのビットを4つあるうち1つ以上選択する($03、$08、$0D、$12)
・サウンド出力レジスタを'0'にする($1FのD4)
   ※特定条件下でサウンド出力レジスタを'1'の場合でも出力されます。
の3つの条件が全ての揃った時に音が出力されます。(例外も多くあります)

音を止めるには、
・音量レジスタを0にする($02、$07、$0C、$11)
   ※($02、$07、$0C、$11)のD0とD1が'0'の場合
・オクターブ合成レジスタのビットを4つとも'0'にする($03、$08、$0D、$12)
・サウンド出力レジスタを'1'にする($1FのD4)
   ※特定条件下でサウンド出力レジスタを'0'の場合でも出力されます。
のどれか1つの条件が合致した時、音が止まります。(例外も多くあります)

例:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
「2.仕様」の楽音周波数の設定の(例)の音を出力したい場合

[クリスタルオシレーター8MHz
[分周レジスタDI=1
[楽音周波数設定レジスタTP=000100011100=284
[オクターブ合成レジスタH0=1、H1=1、H2=0、H3=0の時
[
[ft=8×10^6 /(2^1)×32×284=440.14Hz
[
[fso=(0×2^3×ft)&(0×2^2×ft)&(1×2^1×ft)&(1×ft)=880.28Hz & 440.14Hz


これに加えて
音量レジスタL0~L5を全て'1'(最大)にする。($02、$07、$0C、$11)
ここではL0の右隣2つのビット(D1、D0)を'0'にする。($02、$07、$0C、$11)
サウンド出力レジスタを'0'にします。($1FのD4)

こうして初めて880.28Hz と 440.14Hzの合成波(論理AND)が出力されます。
音を止めるには、音量レジスタL0~L5を全て'0'にします。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

レジスタ設定例:~~~~~~~~~~~~~~~~~~~~~~~~~~~~
クリスタルオシレーター8MHz
チャンネルAで880.28Hz と 440.14Hz合成波(論理AND)を出力する。

$00に00011100
$01に00000001
$02に11111100
$03に00000011
$1Fに01000000

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
※音量レジスタはL0がMSB、L5がLSBとほかのレジスタとは逆になっています。

エンベロープについて
このKY-3201P SGCにはエンベロープ機能が付いていると思われます。
ですが仕様は今現在判明していません。情報を提供していただけるととても助かります。

ノイズ音について
ノイズ音はドラムセットとして利用できるのですが、エンベロープに依存しているためエンベロープの仕様が判明した後説明します。

4.テスト

Arduinoで「ぼくのフレンド」を演奏してみました。
構成はオクターブ合成なし1矩形波×4チャンネル+ノイズなしです。

まだまだ判明していない仕様も多いですが、とりあえず音は出ました。
プログラムは後日ある程度まとまったら公開します。



4.5制作例(2019/10/30追加)


Bサイズのユニバーサル基板の上にこの音源ICを8個のせて、
右4個:矩形波16和音+ノイズ4音、左4個:矩形波16和音+ノイズ4音のステレオにして演奏してみました。


音量が大きめなので下げてから再生することをおすすめします。

MIDIファイルはこちらからどうぞ


5.プログラム(2019/8/5追加)(不具合あり)


エンベロープの仕様が未だに分かりませんが、とりあえずMIDIで演奏できるようにしました。
UARTなので直接MIDI入力はできませんがフォトカプラと抵抗を用意するとMIDI信号を受信できます。各自調べてください。


ドラムのエンベロープはソフトウェアで制御しているので処理落ちで発音が安定しません。各自プログラムを書き直して使用してください。

このプログラムではKY3201P-SGCを4つ使用していますが、1つからでも使用可能です。
構成は、
ドラム:ノイズ4音+タム矩形波1音(同時発音)
矩形波:15チャンネル(同時発音)
です。


回路図
EMIフィルはノイズが気になる方は付けて下さい。
arduinoで使用されているマイコンATmega328pを単体で使用していますが、プログラムはarduinoのスケッチとなっています。

プログラム&回路図(29kb)


KY-3201Pを8個使用したプログラム(2023/8/31追加)


・回路図



・プログラム(Arduino ATmega328P )
//KY3201P-SGC演奏プログラム(受信用)
//Ver. 1.1
//予告なしにバージョンが変わることがあります。
//https://oykenkyu.blogspot.com/2019/06/ky-3201p-sgc-psgic.html
//↑使い方等はこちらから
//プログラミング oy
// 素人が作ったプログラムなのでバグや文法やツッコミたいところがたくさんあると思います。
// ご理解よろしくお願いします。

//00000000000000000000000000000000000000000000000000000000000000
//プログラムの再配布、改変したものを配布する場合は
//「https://oykenkyu.blogspot.com/2019/06/ky-3201p-sgc-psgic.html」
//へのリンクまたはURLを分かりやすいところに貼るもしくは書いて下さい。
//※プログラムソースのコメント欄にも要記載
//00000000000000000000000000000000000000000000000000000000000000

///////////////////////////概要//////////////////////////////////////////
//  このプログラムはPSG音源IC「KY3201P-SGC」で演奏させることができるプログラム(受信用)です。
//  使用するには本プログラムを書き込んだマイコン以外にも送信用PCまたは送信用マイコンが必要です。
//  送られてきたMIDIデータを元にその周波数の矩形波を出力する事ができます。
//  ドラムのエンベロープはソフトウェアで制御しています。バグが多く、正常に発音出来ないことが多いです。
//  今のところ最大8つ「KY3201P-SGC」を接続し、最大同時発音数を16chにできます。
/////////////////////////////////////////////////////////////////////////

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


//#define XTAL 24576000//水晶
#define XTAL 16000000//水晶
//#define SERIALSPEED_DEF 38400 //UARTのボーレート
#define SERIALSPEED_DEF 31250//ボーレート(MIDIは31250bps)

#define SERIALSPEED (SERIALSPEED_DEF*(16000000.0/XTAL)) //@24.576MHz_38400


#define CH_MIX_ENABLE 0//1で1つのチャンネルのみ和音、0でチャンネルごとに和音なし
//  PC送信用ソフト「SMF_PLAY」で演奏する場合は 0
//  MIDI出力できるキーボード等で演奏する場合は 1


#define MUSIC_CODES 16//1つのチャンネルの和音数、CH_MIX_ENABLEが1の時の最大和音


/*<><><><><><><><><><><><><><><><><><>ここから下はプログラム<><><><><><><><><><><><><><><><><><><><>*/


//arduinoピン設定

//D0~D7
const int pinsdt[] = { 2, 3, 4, 5, 6, 7, 8, 9 };//データピン

//A0~A4
const int pinsad[] = { 14, 15, 16, 17, 18};//アドレスピン

//CS0,CS1,CS2,CS3
const int pinscs[] = {13, 12, 11};//チップセレクトアドレスピン
const int we = 10;//書き込みピン
const int IC = 19;//リセットピン

/////////
unsigned int envlope=0;//エンベロープ保存
int insto[16];//楽器保存
int i;//汎用ループ
char midinote[16][16];//MIDIノートオンベロシティ保存

// MIDI受信用
char midi_main_vel[16] = {127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127}; // メインベロシティ(YM2151では未使用)(ノートベロシティと乗算して使用)
char midi_pan[16] = {64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64};                      // パン

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

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

volatile unsigned char drum_freq[5] = {1, 1, 1, 1, 1};                 //ノイズ周波数
volatile char drum_veldo[5] = {0, 0, 0, 0, 0};                         //ノイズ音量減衰
volatile unsigned long drumint_cm[5] = {1000, 1000, 1000, 1000, 1000}; //ドラムタイマーカウント最大値
volatile unsigned int tam_freq = 1;                                    //タム周波数
volatile unsigned int tam_freqdo = 0;                                  //タム周波数減衰

int recym_en = 0;//リバースシンバル発音中か?

    //ここの変数を変更すると音程が変わります。(初期段階では音階が合っていません。気になる方は要変更)
    const unsigned int noteFreq[] = {
        //4, 9,
        9, 10, 10, 11, 12, 12, 13, 14, 15, 15,
        16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31,
        33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62,
        65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 124,
        131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
        262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
        523, 554, 587, 622, 659, 699, 740, 784, 831, 880, 932, 988,
        1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,
        2093, 2218, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
        2093, 2218, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
        2093, 2218,
        //2093, 2218, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
        //4186, 4435, 4699, 4978, 5274, 5587, 5920, 6272, 6645, 7040, 7459, 7902,
        //8372, 8870, 9397, 9956, 10548, 11175
        //, 11840,  12544

};


//----------------------------------------


//タイマー割り込み~~~~~~~~~~~~~~~~~~~~~~~~~
void tim1()//ソフトウェアドラム制御用
{
  int drumint_velmem;
  static unsigned char volnoimem=0;
  for(int i=0;i<5;i++){
   
    if(drumint[i]==1){//ドラム発音
      if(drumint_t[i]==0){//最初の一回だけ実行
        if(intwr!=1){//書き込み中か?
          drumint_t[i]=1;//トグルセット
          switch(i){
            case 0:
            case 1:
            case 2:
            case 3:
              ky3201p_write(i,0x14, 0x0f & drum_freq[i]);
              ky3201p_write(i + 0x04,0x14, 0x0f & drum_freq[i]);
              if(drum_veldo[i]<0){
                drumint_vel[i]=1;//リバースシンバルとか
              }else{
                drumint_vel[i]=0x7f;
              }
             
              //drumint_vel[i]=drumvel[i];
              drumint_velmem=(drumint_vel[i]*drumvel[i])>>7;
              volnoimem = (drumint_velmem >> 5) & 0B00000010;
              volnoimem |= (drumint_velmem >> 3) & 0B00000100;
              volnoimem |= (drumint_velmem >> 1) & 0B00001000;
              volnoimem |= (drumint_velmem << 1) & 0B00010000;
              volnoimem |= (drumint_velmem << 3) & 0B00100000;
              volnoimem |= (drumint_velmem << 5) & 0B01000000;
              volnoimem |= (drumint_velmem << 7) & 0B10000000;
              ky3201p_write(i,0x15, (0B11111110 & (volnoimem)));
              ky3201p_write(i + 0x04,0x15, (0B11111110 & (volnoimem)));
              //write(i,0x15, drumint_vel[i]);
              //write(i + 0x04,0x15, drumint_vel[i]);
             
              break;
            case 4://タム
              ky3201p_write(2,0x05, 0xff & (tam_freq));//OTO10
              ky3201p_write(2,0x06, 0x0f & (tam_freq>>8));//OTO10
              ky3201p_write(2 + 0x04,0x05, 0xff & (tam_freq));//OTO10
              ky3201p_write(2 + 0x04,0x06, 0x0f & (tam_freq>>8));//OTO10
              drumint_vel[i]=0x3f;
              //drumint_vel[i]=drumvel[i]>>1;
              drumint_velmem=(drumint_vel[i]*drumvel[i])>>7;
                volnoimem = (drumint_velmem >> 3) & 0B00000100;
                volnoimem |= (drumint_velmem >> 1) & 0B00001000;
                volnoimem |= (drumint_velmem << 1) & 0B00010000;
                volnoimem |= (drumint_velmem << 3) & 0B00100000;
                volnoimem |= (drumint_velmem << 5) & 0B01000000;
                volnoimem |= (drumint_velmem << 7) & 0B10000000;
             
             
              ky3201p_write(2,0x07, volnoimem);
             
              ky3201p_write(2 + 0x04,0x07, volnoimem);
              //write(2,0x07, drumint_vel[i]<<2);
              //write(2 + 0x04,0x07, drumint_vel[i]<<2);
              break;
          }
        }else{//書き込み中だと次の割り込みで実行
          drumint_c[i]--;
        }
      }
      if((drumint_t[i]==1) && (drumint_veldo_lef[i]>=30) && (drum_veldo[i]!=0)){
        drumint_vel[i]-=drum_veldo[i];
        if (i==4)
        {
          tam_freq+=tam_freqdo;
        }
      }
      if((intwr!=1) && (drumint_t[i]==1) && (drumint_veldo_lef[i]>=30) && (drum_veldo[i]!=0)){//音量制御

        drumint_veldo_lef[i]=0;
        switch(i){
          case 0:
          case 1:
          case 2:
          case 3:
           

            if(0>=drumint_vel[i] || 127<drumint_vel[i]){//出力停止
            drumint[i]=0;//ドラムタイマー無効
            ky3201p_write(i,0x15, 0x00);
            ky3201p_write(i + 0x04,0x15, 0x00);
            }else{
              //drumint_vel[i]-=drum_veldo[i];
              drumint_velmem=(drumint_vel[i]*drumvel[i])>>7;
              volnoimem = (drumint_velmem >> 5) & 0B00000010;
              volnoimem |= (drumint_velmem >> 3) & 0B00000100;
              volnoimem |= (drumint_velmem >> 1) & 0B00001000;
              volnoimem |= (drumint_velmem << 1) & 0B00010000;
              volnoimem |= (drumint_velmem << 3) & 0B00100000;
              volnoimem |= (drumint_velmem << 5) & 0B01000000;
              volnoimem |= (drumint_velmem << 7) & 0B10000000;
              ky3201p_write(i,0x15, (0B11111110 & (volnoimem)));
              ky3201p_write(i + 0x04,0x15, (0B11111110 & (volnoimem)));
            }
           
            break;
            case 4://タム
            if(11>=drumint_vel[i] || tam_freq>=0x0ff0){//出力停止
            drumint[i]=0;//ドラムタイマー無効
           
            ky3201p_write(2,0x07, 0x00);
            ky3201p_write(2 + 0x04,0x07, 0x00);
            }else{
              //drumint_vel[i]-=drum_veldo[i];
              //tam_freq+=tam_freqdo;
                drumint_velmem=(drumint_vel[i]*drumvel[i])>>7;
                volnoimem = (drumint_velmem >> 3) & 0B00000100;
                volnoimem |= (drumint_velmem >> 1) & 0B00001000;
                volnoimem |= (drumint_velmem << 1) & 0B00010000;
                volnoimem |= (drumint_velmem << 3) & 0B00100000;
                volnoimem |= (drumint_velmem << 5) & 0B01000000;
                volnoimem |= (drumint_velmem << 7) & 0B10000000;
             

              ky3201p_write(2,0x05, (tam_freq));//OTO10
              ky3201p_write(2,0x06, (tam_freq>>8));//OTO10
             
              ky3201p_write(2 + 0x04,0x05, (tam_freq));//OTO10
              ky3201p_write(2 + 0x04,0x06, (tam_freq>>8));//OTO10

              ky3201p_write(2,0x07, volnoimem);
              ky3201p_write(2 + 0x04,0x07, volnoimem);
            }
              break;
          }
         
      }
     
     
      if(drumint_cm[i]<=drumint_c[i]){//出力停止
        drumint[i]=0;//ドラムタイマー無効
      }
      drumint_c[i]++;
    }
    else{//ドラム発音停止
      if(drumint_t[i]==1){//最初の一回だけ実行
        if(intwr!=1)//書き込み中か?
        {
          drumint_c[i]=0;//カウントリセット
          drumint_t[i]=0;//トグルリセット
          switch(i){
            case 0:
            case 1:
            case 2:
            case 3:
              ky3201p_write(i,0x15, 0x00);
              ky3201p_write(i + 0x04,0x15, 0x00);
              break;
            case 4://タム
              ky3201p_write(2,0x07, 0x00);
              ky3201p_write(2 + 0x04,0x07, 0x00);
              break;
          }
        }else{//書き込み中だと次の割り込みで実行
       
        }
      }
     
    }
    drumint_veldo_lef[i]++;
  }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//セットアップ'''''''''''''''''''''''''''''''''''''''''''''''''''''
void setup()
{
 
  //データピン
  for (int i = 0; i < 8; i++) {
    pinMode(pinsdt[i], OUTPUT);
  }
  //アドレスピン
  for (int i = 0; i < 5; i++) {
    pinMode(pinsad[i], OUTPUT);
  }
  //チップセレクトピン
  for (int i = 0; i < 3; i++) {
    pinMode(pinscs[i], OUTPUT);
  }
 
  pinMode(we, OUTPUT);
  //MIDIノートオンベロシティ保存
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 16; j++) {
      midinote[j][i]=0;
    }
  }
 
  pinMode(IC, OUTPUT);
 
  //KY3201Pリセット
  digitalWrite(IC, LOW);
  delayMicroseconds(100);
  digitalWrite(IC, HIGH);
  delay(100);
 
 
  //@@@@@@KY3201Pレジスタ設定@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
  for(int i=0;i<=8;i++){//KY3201P,8個
   
    for(int j=0;j<=0x1f;j++){//初期化
      ky3201p_write(i,j,0);//KY3201Pレジスタアドレス0x00から0x1Fまですべて0を書く
    }
 
  //和音設定
  //4チャンネルすべてハードウェア和音無し
  ky3201p_write(i,3,0B00000001);
  ky3201p_write(i,8,0B00000001);
  ky3201p_write(i,13,0B00000001);
  ky3201p_write(i,18,0B00000001);

  //クロック分周
  //write(i,0x1F,0B11000000);//32MHzオシレータ(分周で内部クロック4MHz)
  //write(i,0x1F,0B10000000);//16MHzオシレータ(分周で内部クロック4MHz)
  ky3201p_write(i,0x1F,0B01000000);//8MHzオシレータ(分周で内部クロック4MHz)
  //write(i,0x1F,0B00000000);//4MHzオシレータ(分周なしで内部クロック4MHz)
  }
  //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
  Serial.begin(SERIALSPEED);//シリアル通信開始
 
  Timer1.initialize((int)((float)(2000*(XTAL/16000000.0))));        // 1000μs毎にtim1( )割込み関数を呼び出す
  Timer1.attachInterrupt(tim1); // タイマー割り込み開始
}

//'''''''''''''''''''''''''''''''''''''''''

//メイン(改変非推奨)************************************************************************
void loop()
{
  while (1)
  {
    if (Serial.available() > 0)
    {
      midi_read(Serial.read()); // イベント処理
    }
  }
}


void ky3201p_write(char csp, char adr, char dat) {
  intwr=1;//書き込み中のため書き込み要求禁止
  delayMicroseconds(5);
  //アドレス書き込み
  for (int i = 0; i < 5; i++) {
    digitalWrite(pinsad[i], bitRead(adr, i));
  }
  //チップアドレス書き込み
  for (int i = 0; i < 3; i++) {
    digitalWrite(pinscs[i], bitRead(csp, i));
  }
  delayMicroseconds(5);
  //データ書き込み
  for (int i = 0; i < 8; i++) {
    digitalWrite(pinsdt[i], bitRead(dat, i));
  }
  //書き込み
  digitalWrite(we, LOW);
  delayMicroseconds(5);
  digitalWrite(we, HIGH);
  intwr=0;//書き込み要求許可
}

void noteon(int ch, int num, int inst, int vol, int pan) {//ノートオン
  int cscom = ch >> 2;//cs判定
  ch = ch & 0x03;
  if(0<=ch && 3>=ch){
    cscom=0;
  }
  else if(4<=ch && 7>=ch){
    ch-=4;
    cscom=1;
  }
  else if(8<=ch && 11>=ch){
    ch-=8;
    cscom=2;
  }
  else if(12<=ch && 15>=ch){
    ch-=12;
    cscom=3;
  }
  while(num<0){
    num+=12;
  }
  while(num>=128){
    num-=12;
  }
  //r
  ky3201p_write(cscom,(0x00 + ch*5), 0xff & noteFreq[128-num]);//周期テーブルからデータ書き込み
  ky3201p_write(cscom,(0x01 + ch*5), 0xff & (noteFreq[128-num]>>8));//周期テーブルからデータ書き込み
  //l
  ky3201p_write(cscom + 0x04,(0x00 + ch*5), 0xff & noteFreq[128-num]);//周期テーブルからデータ書き込み
  ky3201p_write(cscom + 0x04,(0x01 + ch*5), 0xff & (noteFreq[128-num]>>8));//周期テーブルからデータ書き込み
 
  //int volr=(int)((long)((vol*pan)>>7));
  //int voll=(int)((long)((vol*(0x7F-pan))>>7));
  int volr=(vol*pan)>>7;
  int voll=(vol*(0x7F-pan))>>7;
  int volsubmemr=0;
  volsubmemr |= (volr >> 5) & 0B00000010;//ベロシティバイトミラー
  volsubmemr |= (volr >> 3) & 0B00000100;
  volsubmemr |= (volr >> 1) & 0B00001000;
  volsubmemr |= (volr << 1) & 0B00010000;
  volsubmemr |= (volr << 3) & 0B00100000;
  volsubmemr |= (volr << 5) & 0B01000000;
  volsubmemr |= (volr << 7) & 0B10000000;
  int volsubmeml=0;
  volsubmeml |= (voll >> 5) & 0B00000010;//ベロシティバイトミラー
  volsubmeml |= (voll >> 3) & 0B00000100;
  volsubmeml |= (voll >> 1) & 0B00001000;
  volsubmeml |= (voll << 1) & 0B00010000;
  volsubmeml |= (voll << 3) & 0B00100000;
  volsubmeml |= (voll << 5) & 0B01000000;
  volsubmeml |= (voll << 7) & 0B10000000;
  ky3201p_write(cscom + 0x04,(0x02 + ch*5), (0B11111100 & (volsubmemr<<2)) | (0B00000011 & envlope));//ベロシティ書き込み
  ky3201p_write(cscom,(0x02 + ch*5), (0B11111100 & (volsubmeml<<2)) | (0B00000011 & envlope));//ベロシティ書き込み
 
}

void noteoff(int ch) {//ノートオフ送信
  int cscom;//cs判定
 
  if(0<=ch && 3>=ch){
    cscom=0;
  }
  else if(4<=ch && 7>=ch){
    ch-=4;
    cscom=1;
  }
  else if(8<=ch && 11>=ch){
    ch-=8;
    cscom=2;
  }
  else if(12<=ch && 15>=ch){
    ch-=12;
    cscom=3;
  }
  ky3201p_write(cscom,(0x02 + ch*5), (0x00));
  ky3201p_write(cscom + 0x04,(0x02 + ch*5), (0x00));
}


// MIDI受信用関数/////////////////////////////
// 1バイトずつmidi_readを実行し、1グループのmidiメッセージを受信し終えるとmidi_comを実行
void midi_read(char read_buf)
{
  if (ex_mess_en == 1)
  {
    if ((unsigned char)read_buf == 0xF7)
    { // エクスクルーシブ・メッセージ終了
      ex_mess_en = 0;
      stop_byte = dat_ph;
    }
  }
  else if (((read_buf >> 7) & 0x01) == 1)
  {
    // データ始まり検出したら
    dat_ph = 0;
    ex_mess_en = 0;
    read_buf_h = 0xF0 & read_buf;

    if (((0x80 <= (unsigned char)read_buf_h) && (0xB0 >= (unsigned char)read_buf_h)) || ((unsigned char)read_buf_h == 0xE0))
    {
      // 3バイト読み取り
      running_ch = read_buf; // ランニングステータスチャンネルセット
      stop_byte = 2;
    }
    else if (((unsigned char)read_buf_h == 0xC0) || ((unsigned char)read_buf_h == 0xD0))
    {
      // 2バイト読み取り
      running_ch = read_buf;
      stop_byte = 1;
    }
    else
    {
      switch ((unsigned char)read_buf)
      {
      case 0xF0: // エクスクルーシブ・メッセージ
        ex_mess_en = 1;
        stop_byte = 254;
        // Serial.print("ex_mess_en\r\n");//デバッグ用*/
        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++;
  }
}

//データ入力処理
void midi_com(unsigned char *in_midi_mess){
 
  if(in_midi_mess[0]>=0x80 && in_midi_mess[0]<=0x8F){//ノートオフ
    for(int i=0;i<MUSIC_CODES;i++){
      if(in_midi_mess[1]==midinote[in_midi_mess[0]-0x80][i]){
        //発音中と同じノートナンバーを見つけたら発音停止
        midinote[in_midi_mess[0]-0x80][i]=0;
        if(CH_MIX_ENABLE==1){
          //1で1つのチャンネルのみ和音、0でチャンネルごとに和音なし
          noteoff(i);
        }else{
          noteoff(in_midi_mess[0]-0x80);
        }
        if ((in_midi_mess[0] - 0x80 == 0x09) && (in_midi_mess[1] == 127) && (in_midi_mess[2] == 0))
        {
          recym_en = 0;
          drumint[0] = 0; //リバースシンバル発音停止
        }
        break;
      }
    }
  }
  else if(in_midi_mess[0]>=0x90 && in_midi_mess[0]<=0x9F){//ノートオン
    for(int i=0;i<MUSIC_CODES;i++){
     
      if(in_midi_mess[2]==0 && in_midi_mess[1]==midinote[in_midi_mess[0]-0x90][i]){
        //音量0で発音中と同じノートナンバーを見つけたら発音停止
        midinote[in_midi_mess[0]-0x90][i]=0;
       
        if(CH_MIX_ENABLE==1){
          //1で1つのチャンネルのみ和音、0でチャンネルごとに和音なし
          noteoff(i);
        }else{
          //ky3201pのみ
          noteoff(in_midi_mess[0]-0x90);
        }
        if ((in_midi_mess[0] - 0x90 == 0x09) && (in_midi_mess[1] == 127) && (in_midi_mess[2] == 0))
        {
          recym_en = 0;
          drumint[0] = 0; //リバースシンバル発音停止
        }
        break;
      }
      else if((midinote[in_midi_mess[0]-0x90][i]==0) && (in_midi_mess[2]!=0)){//和音空きがあったときノートオン
       
       
        midinote[in_midi_mess[0]-0x90][i]=in_midi_mess[1];
        if(in_midi_mess[0]-0x90==0x09){//ドラムチャンネルはソフトウェアでノイズ音を制御して発音
          midinote[0x09][0]=0;
         
          //drumvel=0x01;
            switch (in_midi_mess[1])
            {
            case 35:
            case 36://バス
              if (recym_en == 0)
              { //リバースシンバル非発音中の時発音
                drum_veldo[0] = 0;
                drumint_t[0] = 0;  //リセット
                drum_freq[0] = 15; //
                drumint_cm[0] = 5; //時間
                drumvel[0]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
                drumint[0] = 1;
              }
             
              break;
            case 38:
            case 40://スネア
              drum_veldo[1]=60;
              drumint_t[1]=0;//リセット
              drum_freq[1]=1;//
              drumint_cm[1]=15;//時間
              drumvel[1]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[1]=1;
              break;
            case 41://タム
              drum_veldo[4]=10;
              drumint_c[4]=0;//カウントリセット
              drumint_t[4]=0;//リセット
              tam_freq=noteFreq[78];//
              tam_freqdo=400;//
              drumint_cm[4]=150;//時間
              drumvel[4]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[4]=1;
              break;
            case 43://タム
              drum_veldo[4]=10;
              drumint_c[4]=0;//カウントリセット
              drumint_t[4]=0;//リセット
              tam_freq=noteFreq[76];//
              tam_freqdo=300;//
              drumint_cm[4]=150;//時間
              drumvel[4]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[4]=1;
              break;
            case 45://タム
              drum_veldo[4]=10;
              drumint_c[4]=0;//カウントリセット
              drumint_t[4]=0;//リセット
              tam_freq=noteFreq[74];//
              tam_freqdo=300;//
              drumint_cm[4]=150;//時間
              drumvel[4]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[4]=1;
              break;
            case 47://タム
              drum_veldo[4]=10;
              drumint_c[4]=0;//カウントリセット
              drumint_t[4]=0;//リセット
              tam_freq=noteFreq[72];//
              tam_freqdo=300;//
              drumint_cm[4]=150;//時間
              drumvel[4]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[4]=1;
              break;
            case 48://タム
              drum_veldo[4]=10;
              drumint_c[4]=0;//カウントリセット
              drumint_t[4]=0;//リセット
              tam_freq=noteFreq[70];//
              tam_freqdo=300;//
              drumint_cm[4]=150;//時間
              drumvel[4]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[4]=1;
              break;
            case 50://タム
              drum_veldo[4]=10;
              drumint_c[4]=0;//カウントリセット
              drumint_t[4]=0;//リセット
              tam_freq=noteFreq[68];//
              tam_freqdo=300;//
              drumint_cm[4]=150;//時間
              drumvel[4]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[4]=1;
              break;
            case 49://シンバル
              drumint_t[3]=0;//リセット
              drum_veldo[3]=8;
              drum_freq[3]=2;//
              drumint_cm[3]=2500;//時間
              drumvel[3]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[3]=1;
              break;
            case 57://シンバル
              drumint_t[3]=0;//リセット
              drum_veldo[3]=8;
              drum_freq[3]=1;//
              drumint_cm[3]=2500;//時間
              drumvel[3]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
              drumint[3]=1;
              break;
            case 42://クローズハイハット
            drumint_t[2]=0;//リセット
            drum_veldo[2]=80;
            drum_freq[2]=1;//
            drumint_cm[2]=5;//時間
            drumvel[2]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
            drumint[2]=1;
              break;
            case 44://足ハイハット
            drumint_t[2]=0;//リセット
            drum_veldo[2]=80;
            drum_freq[2]=2;//
            drumint_cm[2]=15;//時間
            drumvel[2]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
            drumint[2]=1;
              break;
            case 46://オープンハイハット
            drumint_t[2]=0;//リセット
            drum_veldo[2]=40;
            drum_freq[2]=1;//
            drumint_cm[2]=250;//時間
            drumvel[2]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
            drumint[2]=1;
              break;
            case 127://リバースシンバル
            recym_en = 1;
            drumint_t[0]=0;//リセット
            drum_veldo[0]=-10;
            drum_freq[0]=1;//
            drumint_cm[0]=5000;//時間
            drumvel[0]=midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>8;//音量
            drumint[0]=1;
              break;
            default:
           
            break;
            }
        }
       


        if(in_midi_mess[0]-0x90!=0x09){//ドラムチャンネルは周波数発音なし
          /*if(CH_MIX_ENABLE==1){
            //1で1つのチャンネルのみ和音、0でチャンネルごとに和音なし
            noteon(i,in_midi_mess[1],insto[0],in_midi_mess[2]>>1,64);
          }else{
            //ky3201pのみ
            noteon(in_midi_mess[0]-0x90,in_midi_mess[1],insto[0],in_midi_mess[2]>>1,64);
          }*/
          if(CH_MIX_ENABLE==1){
            //1で1つのチャンネルのみ和音、0でチャンネルごとに和音なし
            noteon(i,in_midi_mess[1],insto[0],midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>7,midi_pan[in_midi_mess[0]-0x90]);
          }else{
            //ky3201pのみ
            noteon(in_midi_mess[0]-0x90,in_midi_mess[1],insto[0],midi_main_vel[in_midi_mess[0]-0x90]*in_midi_mess[2]>>7,midi_pan[in_midi_mess[0]-0x90]);
          }
        }
        break;
      }
     
    }
  }
  else if(in_midi_mess[0]>=0xA0 && in_midi_mess[0]<=0xAF){//ポリフォニックキープレッシャー
    //何もしない
  }
  else if(in_midi_mess[0]>=0xB0 && in_midi_mess[0]<=0xBF){//コントロールチェンジ
    switch (in_midi_mess[1])
    {
    case 0x07: // チャンネルボリューム
      midi_main_vel[in_midi_mess[0]-0xB0] = in_midi_mess[2];
      break;
    case 0x0A://パン
      midi_pan[in_midi_mess[0]-0xB0]=in_midi_mess[2];
      //midi_pan[in_midi_mess[0]-0xB0]=0;
    break;
    case 0x78:
    case 0x79:
      for(int i=0;i<MUSIC_CODES;i++){
        //発音停止
        midinote[in_midi_mess[0]-0xB0][i]=0;
        if(i==0){//ky3201pのみ
          noteoff(in_midi_mess[0]-0xB0);
        }
        recym_en = 0;
        drumint[0] = 0; //リバースシンバル発音停止
      }
      break;
   
    default:
      break;
    }
  }
  else if(in_midi_mess[0]>=0xC0 && in_midi_mess[0]<=0xCF){//プログラムチェンジ
    insto[in_midi_mess[0]-0xC0]=in_midi_mess[1];
  }
  else if(in_midi_mess[0]>=0xE0 && in_midi_mess[0]<=0xEF){//ピッチベンド
    //何もしない
  }
  else if(in_midi_mess[0]>=0xF0 && in_midi_mess[0]<=0xFF){//システムメッセージ
    //何もしない
  }
}
//****************************************


いまだに多くの仕様が謎ですがKY-3201Pを8個プログラム使用した場合のプログラムを載せておきます。
4個の時のプログラムはいくつか不具合があるのですが、こちらでは修正してあります。
正常にキーオン・キーオフできない場合がある問題が残っていますが、一応MIDIで演奏できます。

このプログラムではKY3201P-SGCを8つ使用していますが、1つからでも使用可能です。



6.まとめ

このPSG音源IC「KY-3201P」は長らく仕様不明でしたが、「YMZ294」同様に制御しやすい音源ICということがわかりました。
正直、自分でもICのダイを見ただけでここまで解析できるとは思いませんでした。
まだまだ未知のレジスタがあるので楽しめそうです。
この記事を見てくれている皆さんの部品箱の中にたぶんこのICが眠っていると思います。興味のある方はぜひ遊んでみてください。


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






音源IC コレクション 一覧へ



















おわり