*注意*

近年、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.

2024年9月18日水曜日

スーパーファミコンの音源モジュール「SHVC-SOUND」でMIDI演奏 (SNES APU , SPC700の使い方)➁

 

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


ページサイズが大きすぎてインデックスされない問題があるため、記事を分割しました。
の続きです。



ようやくAPUで音を出すのに必要な最低限の手順・プログラムが完成しました。まだMIDIを受信して演奏する機能はありませんが、ひとまず音を出してみましょう。


・回路図




Lチカの時とほぼ同じ回路です。
Lチカの場合は、APU内のSPC700のみでLチカを行っているのを実感できるように、Arduinoとの切り離しが可能回路としました。

今回は、Arduinoとの切り離しはしないため、プルアップ・プルダウン抵抗を取り除きました。(Lチカの時の回路でも音を出すことができます。)

ただし、データバスについては、Arduinoのプログラム上でゲート開放となるタイミングがあるので、プルアップ・プルダウン抵抗を付けておくことをおすすめします。

APU内のオペアンプの電源電圧は、本来9Vなのですが回路の簡略化のため、5Vを使用しています。気になる方は、「A_Vcc」を5Vから9Vへ変えてみてください。


・Arduinoのプログラム

MMLを演奏します。
プログラム内でTimerOneライブラリを使用しています。

BRRサンプルデータは、
のピアノサンプル「piano1」と「piano8」を用いました。Arduinoのプログラムには、著作権の都合上BRRサンプルを同胞していません。
各自用意するか、上記サイトからBRRファイルをダウンロードしてみてください。
(上記サイトのBRRデータの初めの2バイトは、ループ開始位置(BRRデータのバイト数)を示しています。3バイト目以降をArduinoのプログラムメモリに配置してください。)

BRRファイル内のバイナリデータをCプログラムの配列記述にするには、Linuxのxxdコマンドを使います。
Windowsの場合は、
を使います。


//SPC700でMML演奏プログラム
//©oy
//https://oykenkyu.blogspot.com/2021/10/spc700-midi.html

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

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


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

//ユーザープログラムのサイズ(Byte)
#define PROGSIZE 172

//SPC700プログラム書き始めアドレス
#define WRITE_ADR 0x0200

//SPC700プログラム開始アドレス
#define START_ADR 0x0200

//BRRサンプルベクタテーブルアドレス
//このアドレスは、BRRサンプルベクタの場所を示しています。
#define BRR_VCT 0x0700


//DSPレジスタ名
#define VOL_X_L 0x00
#define VOL_X_R 0x01
#define PTC_X_L 0x02
#define PTC_X_H 0x03
#define SRCN_X 0x04
#define ENV1_X 0x05
#define ENV2_X 0x06
#define GAIN_X 0x07
#define DSP_ENVX_X 0x08
#define DSP_OUTX_X 0x09
#define MAIN_VOL_L 0x0C
#define MAIN_VOL_R 0x1C
#define ECHO_VOL_L 0x2C
#define ECHO_VOL_R 0x3C
#define KEY_ON 0x4C
#define KEY_OFF 0x5C
#define DSP_CONF 0x6C
#define END_SMPL 0x7C
#define ECHO_FB 0x0D
#define PTC_MD 0x2D
#define NOISE_EN 0x3D
#define ECHO_EN 0x4D
#define SR_DIR 0x5D
#define ECHO_BUF_ADR 0x6D
#define ECHO_DLY 0x7D
#define ECHO_FIR_X 0x0F

//DSPチャンネル名
#define CH0 0x00
#define CH1 0x10
#define CH2 0x20
#define CH3 0x30
#define CH4 0x40
#define CH5 0x50
#define CH6 0x60
#define CH7 0x70

//DSPレジスタのビット
#define DSP_RESET_EN 0x80
#define DSP_MUTE_EN 0x40
#define DSP_ECHO_DEN 0x20
#define DSP_ENV_ADSR_EN 0x80

#define MML_MAX_TR 8//MML演奏最大トラック数(SNESのAPUはMAX 8チャンネル)


char timeout = 0;//APU書き込みタイムアウト検出用

//デフォルトオクターブ[ch0,ch1,ch2,…,]
//増やすとオクターブが高くなります。(出力オクターブ = brr_oct + (mmlのオクターブ))
char brr_oct[8] = {0, 0, 2, -1, -1, -1, -1, 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;//テンポ


//ユーザープログラム
//SRAM・DSPとマイコン間データ転送プログラム
//プログラム詳細
//https://oykenkyu.blogspot.com/2021/10/spc700-midi.html
unsigned char progdata[PROGSIZE] = {
  0x8F, 0x00, 0x00, 0x8F, 0x00, 0x01, 0x8F, 0x00, 0xF5, 0x8F, 0x00, 0xF6, 0x8F, 0x00, 0xF7, 0xE4,
  0xF4, 0x28, 0x3F, 0xC4, 0x04, 0xFA, 0x04, 0xF4, 0xE4, 0xF4, 0x44, 0x04, 0x28, 0x01, 0xD0, 0x0A,
  0xE4, 0xF4, 0x44, 0x04, 0x28, 0x02, 0xD0, 0x43, 0x2F, 0xEE, 0xE4, 0xF4, 0x28, 0x10, 0xF0, 0x0A,
  0x18, 0x10, 0x04, 0x3F, 0x50, 0x02, 0x3A, 0x00, 0x2F, 0x0A, 0x38, 0xEF, 0x04, 0xBA, 0xF6, 0xDA,
  0x00, 0x3F, 0x50, 0x02, 0x58, 0x05, 0x04, 0xBA, 0x00, 0xDA, 0xF6, 0xFA, 0x04, 0xF4, 0x2F, 0xC8,
  0xE4, 0xF4, 0x28, 0x20, 0xF0, 0x0B, 0x18, 0x20, 0x04, 0xFA, 0x00, 0xF2, 0xFA, 0xF5, 0xF3, 0x2F,
  0x09, 0x38, 0xDF, 0x04, 0x8D, 0x00, 0xE4, 0xF5, 0xD7, 0x00, 0x6F, 0xE4, 0xF4, 0x28, 0x10, 0xF0,
  0x0A, 0x18, 0x10, 0x04, 0x3F, 0x91, 0x02, 0x3A, 0x00, 0x2F, 0x0A, 0x38, 0xEF, 0x04, 0xBA, 0xF6,
  0xDA, 0x00, 0x3F, 0x91, 0x02, 0x58, 0x0A, 0x04, 0xBA, 0x00, 0xDA, 0xF6, 0xFA, 0x04, 0xF4, 0x2F,
  0x87, 0xE4, 0xF4, 0x28, 0x20, 0xF0, 0x0B, 0x18, 0x20, 0x04, 0xFA, 0x00, 0xF2, 0xFA, 0xF3, 0xF5,
  0x2F, 0x09, 0x38, 0xDF, 0x04, 0x8D, 0x00, 0xF7, 0x00, 0xC4, 0xF5, 0x6F
};


//BRRサンプル0
//著作権の関係上ここには同胞していません。
//演奏デモでは「https://www.smwcentral.net/?p=section&a=details&id=17248」の"piano2 g#2-d#3.brr"を使用
//上記brrデータの先頭2バイトは、ループ先アドレスを示すので、3バイト目以降を下の配列に入れてください。
unsigned int brrsample0_len = 5634;//BRRサンプル0サイズ
const unsigned char PROGMEM brrsample0[5634] = {
};


//BRRサンプル1
//著作権の関係上ここには同胞していません。
//演奏デモでは「https://www.smwcentral.net/?p=section&a=details&id=17248」の"piano8 c6-e6.brr"を使用
//上記brrデータの先頭2バイトは、ループ先アドレスを示すので、3バイト目以降を下の配列に入れてください。
unsigned int brrsample1_len = 5634;//BRRサンプル1サイズ
const unsigned char brrsample1[5634] PROGMEM = {
};


//MIDIのノート番号からDSPのピッチを求めるテーブル(BRRサンプリング周波数32000Hzで440Hz(A)の音程の楽器を録音)
const unsigned int noteFreq[128] = {
  //A, A#, B, C, C#, D, D#, E, F, F#, G, G#
  /**/ /**/ /**/ 76, 81, 85, 91, 96, 102, 108, 114, 121,
  128, 136, 144, 152, 161, 171, 181, 192, 203, 215, 228, 242,
  256, 271, 287, 304, 323, 342, 362, 384, 406, 431, 456, 483,
  512, 542, 575, 609, 645, 683, 724, 767, 813, 861, 912, 967,
  1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933,
  2048, 2170, 2299, 2435, 2580, 2734, 2896, 3069, 3251, 3444, 3649, 3866,
  4096, 4340, 4598, 4871, 5161, 5468, 5793, 6137, 6502, 6889, 7298, 7732,
  8192, 8679, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596,
  15464, 16383, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596,
  15464, 16383, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596,
  15464, 16383, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777
};

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

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

//よいまちカンターレ
const unsigned char PROGMEM mml_data[]=
"t171l16r1r1r8o7d8>a8<degf+ed>a8>a8r8<a8<d8>a8<gf+8.d8e8r8d8>a8<degf+ed>a8>a8r8<a8<d8>a8<gf+8.e8d8r8d8>b8<degf+ed>a8>a8r8<a8<d8>a8<gf+8.d8e8r8d8>b8<degf+ed>a8b8r8<d8e8f8>b<c+deff+gg+a4.r2r8>b8b8r2.b8b8r2r8>e4r8f+8r8g8r8g+8r8<<d8d8.c+64c32>b64r2..<abr2r8a8a8br2r8.d8g+8ar2r8.>e8<d8r2.g+8g+8r2..a8ar2r>b4a+8b8<c+4d+8e8f8d8d8.c+64c32>b64r2r8<e8d8dr2r8.d8rar2.e8d8<dr2r8.>>>>a4.r8g+2g4.r8b8.ra8b8a2<c8>a8<c8>a8b4<drdr8.>grb8r8a8<<e32r32e32r32a8r8>>a8g8d8<<a32r32e32r32>>f+8.<<e32r32a8r8>>f+4<a+8ra+32r32ar8.b8.rar8.f+rerb8e4.g8r8er8.<c+r8.eree>ererc+8r8>a+2r8<frf8.r>a+8r8<a+4a8.<dega4.g+32g32f+32f32e32r1r1r4...>>a4a8a8r8a8g+8a8b4b8br2r8.<<g+2..b4.r8<c4.>g+8<c+2.>g+8<c+8d1^d8>a2<c+2>b2<c2>e2..f+2g+4g+8g+8g+8e2..f+2a4.c+ef+g+b4.<c2>g+1^g+8a1g+1b4>b4<e4>b4c2d2r2.g+8.r>b8.b8rb8.rg+8b8b3r6g+8g+8<c4c+4.<<e4d+4>b4a1b1<c1^c1e1e4>b4<e4g+4r1r1r1r1r1r1r1r1r1r1r1r1r2..cr8.c8>g8<c8c32r32c32r32>b32r32b32r8r32d8rddc+64c64>b64a+64g+64f+64e64d+64r8.e4f+4r2<g+2..b4.r8<c4.>g+8<c+2.>g+8<c+8d1^d8>a2<c+2>b2<c2>e2..f+2g+4g+8g+8g+8e2..f+2a4.c+ef+g+b4.<c2>g+1^g+8a1g+1b4>b4<e4>b4<e4.r8f+4.r1r1r1r4.>e2.r8e8e8e4r8;l16r1r1r8o6d8>a8<degf+ed>ar>ar8.<a8<d8>a8<gf+8.d8er8.d8>a8<degf+ed>a8>a4<a8<d8>a8<gf+8re8d8r8d8>b8<degf+ed>a8>a8.r<a8<d8>a8<gf+8rd8e8r8d8>b8<degf+ed>a8b8.r<drerfr>b<c+deff+gg+ara8.^a64g+64g64f+64e64d+64d64c64>a+64a64r2r32a.r32a.r2.r32a.r32a.r4...r8.a4a8b8b8<c8c8c+8c+8r8>drd8>br<d8rdr8d8r8drdr>b32r32b<f+32r32f+re8rd8r8drdr>br<d8rd8.e8g8.rf+8.re8.rd4r8drd8>br<d8rdrd32r32d8r8drdr>br<d8rdr8drd4r2r8g4f+8g8a4b8<c8c+8r8>drd8>br<d8rdr8e8r8d32r32d32r32dr>br<f+8.e8rd8r8drdr>br<d8rd8.e8g8.rf+re8.rd4r4drd8>br<d8re8rf+8a8.r>br8.<d8rc+8rdrd4r4d8e8f+8g8a2r4d4a8.g8.f+4.g8.rf+8.rf+8.rb8.rf+8.re8..r32e8d2^d8r2d8.e8.f+ra8.rc+rc+rd8.c+6r48c+8d1r2e8rd8ra4r1r1r2.e8.re8er8.e8g+re8b8.rb8e8g+8.rf+re8f+re8.r<c+8.r>b8<c+8r8>g+8..r32g+rf+8e8f+8e8f+8g+8e8.r<c+8.r>b8<c+r>g+rg+8.rg+8f+8e8f+8e8g+8r8c+8.r4re8.r4rf+4r4g+4r4<c+8.r>b.r32b8g+.r32e8r8b8.r<c+8r8>g+8.rg+.r32f+.r32e.r32e8.r<c+8.r>b8<c+8r8>g+8.rg+rf+8e8f+8e8f+8g+.r32g+8.r8.<e8.rd+8.re2^e6r6r24>c+8d+8e8.rere4f+8.re8.re.r32e8>b8<e8.f+8.g+8b2..r4b8b8e8drerf+4e8r2r8b8.rb8.a8rg+8.rg+8<c+8>b3r6b8b8d+4e2^e6r6r24c+8d+8e4..r8.c+8c+8d+8e3r24e8.r>b8<e8f+8a8.g+8.e2..r4e8rer8b1^b1^b8r1r1r1e8r8<c+8.r>b8<c+8r8>g+8r8g+8r8erf+8e8f+8g+8e8.r<c+8.r>b8<c+8r8>g+8.rg+8f+8e8f+8.rg+8.rc+8.r4re8.r4rf+8.r4rg+8.r4r<c+8r8>brb8r8g+4b8.r<c+4>g+4g+rf+8.e1r1r1r1.re8.r<c+8.r>b8<c+8r8>g+4g+rf+8e8f+8e8f+8g+8e8.r<c+8.r>b8<c+r>g+rg+8.rg+8f+8e8f+8.rg+8.rc+8.r4re8.r4rf+4r4g+4r4<c+8.r>b8b8g+8e8r8b8.r<c+8r8>g+8.rg+8f+8e8e8.r<c+8.r>b8<c+8r8>g+8.rg+rf+8e8f+8e8f+8g+rg+8.r8.<e4d+8.re2^e6r6r24>c+8d+8e4ere4f+4e4e8e8>b8<e8.f+8.g+rb2..r4b8r8ererf+e32f32f+32e32d+32f32f+.r32e8.r1r1r1r>b2.r8b8b8b4r8;l16r1r1o3d8d8r4.>f+4a4r8a8a+4r8a+8b8b8b8r4.b8<e8d4d8r8>f+4f+8f+8g8g8g8g8g8g8g8g8f+8f+8f+8f+8f8f8f8f8f8e8e8e8f+8f+8f+8f+8g+8g+8g+8g+8a+8a+8a+8a+8f+8a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8a8a8b8b8<c8c8c+8c+8d8d8r4d8rd8r8.>g+8g+8r4g+8rg+8r8.g8g8r4g8rg8r8.g8g8g8g8d8<d8>d+8<d+8>e8e8>e8r8<e8re8>e8r<a8a8>a8r<a8.ra8.d+8d8d8c8>a8<d8c8d8g4f+8r8a4<f+8r4d8d8>d8d8d8d8d8d8<d8d8>d8d8d8d8d8d8<d8d8>d8d8d8d8d8d8<d8d8d8d8a8d8f+4>b4b8b8a+4a+8a+8a4a8a8a8.a8.a8d4d8<d8d8d8>a8g+8g4g8g8<d8>g8<d8>g8g4d8d8d8g8<d8c+8>f+4f+8f+8a+4a+8a+8b4b8b8f+8f+8g+8a+8e4e8e8b8e8b8e8f+4f+8f+8<c+8>f+8<c+8>g8g4g8g8g8g8g8g8g4g8g8g8g8g8a8a8a8g+8g<g8r>f+8g8g+8a8a8g+8g<g8r>f+8g8g+8a8a8g+8g<g8r>f+8e8a8a8a8a8r8a8g+8a8b4b8b8r2r8e8<e8>e8<e8>e8<e8>g8b4b8b8<c4c8>g+8<c8c+8c+8c+8c+8c+8>g+8g8e8e8<e8>e8<e8>e8<e8>e8f8f+4f+8g+8a8a8a8a+8b4b8b8<c4>b8<c8c+8r8c+8c4c8c8>b4b8r8a+4a+8<d8>a+8a8a8a8a8a8a8a+8b4b8b8a4a8a8a8g+8g+8g+8<c4c8c8c+4c+8c+8>e4<e8>e8<e8>f+4f+8f+8f+4f+8f+8g+4g+8g+8g+4g+8g+8a+8a+8a+8a+8a+8a+8a+8a+8<c4c4d4d8c+8>a+8a+8a+8a+8a+8a+8a+8a+8a4a8a8a8a8a8a8g+8<g+8>g+8g+8<c8c8c4c+8c+8c+8e4c+8>g+8g8f+8f+8f+8f+8f+8f+8f+8g8g+8g+8g+8g+8g+8g+8g+4a8a8a8a8a8<a8>a8<a8>a8<a8>a8<a8>a8<<e8>>a8a+8b4<b8>b8b8b8b8b8b8b8b8b8b8b8b8b8<<d2>b2<c2>a2r1r1r1r1r1r1r1r1r1r1r4.>c8<c32c+64d64d+64e64f+64g32.f+64f64e64d+64d32c+32c32>b<c8c8>g8<c8g8c8>b8<c8d4d8r4c4d4c8>f+g+a+b<c+e>e8<e8>e8<e8>e8<e8>g8b4b8b8<c4c8>g+8<c8c+8c+8c+8c+8c+8>g+8g8e8e8<e8>e8<e8>e8<e8>e8f8f+4f+8g+8a8a8a8a+8b4b8b8<c4>b8<c8c+8r8c+8c4c8c8>b4b8b8a+4a+8<d8>a+8a8a8a8a8a8a8a+8b4b8b8a4a8a8a8g+8g+8g+8<c4c8c8c+4c+8c+8>e4<e8>e8<e8>f+4f+8f+8f+4f+8f+8g+8g+8g+8g+8g+8g+8g+8g+8a+8a+8a+8a+8a+8a+8a+8a+8<c2d2>a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8a8a8g+8gf+<g8>f+8g8g+8r4e2.r8e8e8e4.;l16r1r1r8o4a8r2r8a4.r8<a+3r24a+8b8r1c4>a8<c4<<<c64>b64a64g32f64e64d64c64>b64a64g64f32e64d64c64>b32a32g32.r2..a2g+2r8g4r8f+4.d8f+4.r8a+2^a+6r1r1r1r1r1r1r1r1r1r1r2....r4.g+24a48a+48b32<c48c+48d48>a96a+96<c96c+96d96d+96e96f+96g96g+96a96a+96b96<c96c+96>d+96e96f+96g96g+96a96a+96b96<c96c+96d48d+96e96f96f+96g96g+96r96a96a+96b96<c96c+96d96d+96f96r1r1r1..>>f+4<d4>a8.r<d8.r>g+8.rg4r1r8ar<d4c8.r>f+rd8f+8<d8r2r8>a8f+4<c+8c+8>b8.rc+4a8e8c+8r8f+8.rb8b8f+4<drd8c+8r4>e8g8b8<d8>e8r4.f+8a8<c+8r8>f+8c+8r8<f8r4>>a+8<<e8.r4r>f8.ra+8>a+8<a8.rg8a8r1r1r2..>>e4e8e8r8e8d+8e8f+4f+8f+8r2r8<e8>b8<e8e8>b4<e8>b4b4<c4.c4e4g+4.g+4>b4r8<g+8r8>b8<e8>b8g+8<c+4.r8>f+4.r8f+4.r8<g+4<g+r8.<g+8e8>b8<g+8r8e8>b8<f+8r8>a8f+8<d.r64<a64g64f64e64d64c64>b64a64g32f64e64d64c64>b64a32g64f64e64d32c32>a+64a32g32ee4c+8e4c+8e8f+4f+4f+4.d+8r8b4g+8<c4.r8c+4.e8>g+4e8e8e8f+4f+8f+8f+4f+8f+8e8e8e8e8e8e8e8e8<<b8e8>b8<e8e8.f+8.a8<c8>>e4.f+2>e4.g+4.g+4b4.b4.b4d+4r4g+4.r8c+4.g+4.r4>f+4.f+4.f+4g+4g+8g+4.g+4<<<c2f+2e2c2>>>f+4.f+4.f+4f+4.b2^b8<<<e8c8>g8.re8b8<f+8.r>>c2>a2r4<<<c+8>g+4f+8e8f+8>>e2..b2<c2^c8>c+2..e2b4e4.f+2a2b2<c2>c+4.c2>a+2b2..r1.r8<<c4c8c8r8<<c8>b8<c8d8.rd8r4>>>g4a4r<<ef+g+ab<c+d+>>e8>b8<e8e8>b4<e8>b4b4<c4.c4e4g+4.g+4>b4r8<g+8r8>b8<e8>b8g+8<c+4.r8>f+4.r8f+4.r8<g+4<g+r8.<g+8e8>b8<g+8r8e8>b8<f+8r8>a8f+8<d.r64<a64g64f64e64d64c64>b64a64g32f64e64d64c64>b64a32g64f64e64d32c32>a+64a32g32ee4c+8e4c+8e8f+4f+4f+4.d+8r8b4g+8<c4.r8c+4.e8>g+4e8e8e8f+4f+8f+8f+4f+8f+8e8e8e8e8e8e8e8e8<<b8e8>b8<e8e8.f+8.a8>>g8r4.f+3r1r1r1r4r6b2.r8b8b8b4r8;l16r1r1o7a24f+12df+>ardrdf+a<d<d>ae>ae<ec+>beaa+<c+>a+<c+g+f+48g+24>>a+<de>a+<<f+d>a24<d24f+24>>ar<degra+r<d>agf+a<ec>a>a<deg<e24a24e24>f+rgrdrbgb24<f+12bf+d>b<d>aba<f+d>br<eec+48>a24<e>c+24e24a24<c24d24e24>>g+8<ce<<d>ad>af+ed>bf+b<deec+>af+<<<dc>ba>>g+8<<c24d24f24bfcf>af<af>af+>a+<f+g+rg+24a24r2.r24g+r2...g+r1rc+r1r1r1r1r1r1r2...r4.>a32r32a32r32a32r32a32r32a32r1r4...<d32r32e32r32g32r32a32r3r96e32r32a32r4...f32r.a32r4r32f32r32a32r3r96b32r.f+32r32a32r4r32c32r32d32r32a32r1r8..f+32r.e32r3r96a32r.g+32r.f+32r1r.d>f+r8d24f+24a24<d24a24<c+24d24r12f+24r12e24f+24r24f+24g24a24f+24a24b24<c+24>b24r24<c+24>a24f+24drbaf+r8.ab>c+4f+8<e8e>ea<c+>e>ar8<e>>c+8.<<arer>a+8f+a+<f+>f+<c+8>a8>b<df+ed>b<af+>b<f+<c+>baf+<<e>>g<e24r12e32g32b32r32f+32b32<d32r32d24r48>b24r48g24r48>>bb<ge>b<<c+24e24g+24f+24c+r48>c+ec+>f+c+8<f+>f+<c+24f+24<f+24a24f+24>b24>gda+a<d8>g8d<d>a+ga+gd8a+g<dgg8>g<g>d8dg8ar8r32<e32<eer1r1r1r1r2.r>g+rbr<erer>>br<g+rerg+rg+r<g+rd+r>g+r<d+d+g+rg+rd+d+>g+r<g+g+>g+ec+>g+r8<g+r8eg+r>br<erg+r>br8.g+<e>bg+br4rf+r<er8<<c+32f+32ac+>>>f+rar<f+b<f+b>f+>b<erg+r>g+r<g+>b<erg+r>g+r<cr>g+<cr>g+<crcr>ar<<ef+af+>>br<fr>fa+<fa+<dc>a+eg+ec+g+ec+>aer32<<e32g+32e32g+32r.>e>bf+r8.bf+<ebf+a<c+>baf+ec+>af+<g+f+32b32<d+r>crcrcg+32<d+32f+32r32f+32r32>>g+r<er<<g+d+c+>bg+ec+>bag+ec+>b<c+ec+>f+<c+f+a<c+f+a<c+e>ac+>af+c+>ag+<eeg+b<eg+e<e>bg+>beg+e>bg+<a+rg+rerg+g+<<e>bec+>bg+ec<grecgrcra>f+d>a<<arerdr>eg+<er>g+r<c+>g+<g+e>g+r<c+r>br<f+rer>br<erf+d+>br<br>d+rg+rd+rbr<cr>>g+r<crd+rerc+rc+rg+r8.c+rererer>f+r<<ec+>af+ec+>f+c+>a<c+f+a<brd+r>g+r<d+rg+rd+>g+d+rg+r<ar>ar<cr>erer<cr>erar<er>>ar<crercrer4r>br<f+rbr<erf+r<cr>f+r>brf+b<f+a<cec>a>b<f32r32f+32r32g32r32g+32r32a32r32a+32r32b32r32<e2<e2e2f+2r4<c+8>g+4f+8e8f+8>b2er>>e4<b4>b4<<c8>g+8c8<d+r8.c+4.r4>>c+4<d4>e4r8<<dr4..>f+4<f+r8.c+4>>a4<b4<br8.c4>d+4<g+4>>c+8<<g+4>>c4<<g+4>>>a+4<<<c2^c8<e6c6>a6e6c6>a6e4c4r1..<<ed+c+>bgec+>bagec+r2g+rbr<erer>>br<g+rerg+rg+r<g+rd+r>g+r<d+d+g+rg+rd+d+>g+r<g+g+>g+ec+>g+r8<g+r8eg+r>br<erg+r>br8.g+<e>bg+br4rf+r<er8<<c+32f+32ac+>>>f+rar<f+b<f+b>f+>b<erg+r>g+r<g+>b<erg+r>g+r<cr>g+<cr>g+<crcr>ar<<ef+af+>>br<fr>fa+<fa+<dc>a+eg+ec+g+ec+>aer32<<e32g+32e32g+32r.>e>bf+r8.bf+<ebf+a<c+>baf+ec+>af+<g+f+32b32<d+r>crcrcg+32<d+32f+32r32f+32r32>>g+r<e<<<c+>g+d+c+>bg+ec+>bag+ec+>b<c+ec+>f+<c+f+a<c+f+a<c+e>af+>af+c+>ag+<eeg+b<eg+e<e>bg+>beg+e>bg+<a+rg+rerg+g+<<e>bec+>bg+ec<gdrcgrcra>f+d>a<<arerdr1....ebag+ebag+e>b<ed+e>b<d+er4e>b<er>b<eg+e>b<eg+e>br<b>b<ef+g+>b<er8.;l16r1r1o4d4r4.>f+4<e4.r8f+2r8>b4r4.b8<e8d4.r8f+2^f+8g2..c+2c2r8e4f+4f+4g+2^g+8a+2^a+8>a8a8r2.a8a8r2.a8a8r2r8a4a8b8b8<c8c8c+8c+8d8d8r2.>g+8g+8r2.g8g8r2.g8g8r2.e8e8r2.a8a8r2.d8d8r4d8ddddg4f+8g8a4b8<c8c+8d8d8r2.>a+8a+8r2.<d8d8r2.d8d8r2.>b4.r8a+4.r8a4.r8a8.a8.a8<d2r2>g1^g4d2r4f+2a+2b1e1f+1<<<fdfa+<dr>>a+<<d>e>a+<d>a+<ede>a+<fdfa+<dr>>a+<<d>f>a<d>a>>g8a8e8<e8g+>f+>a<<<g>>>g8a<gg8g+8e8<e8g+>f+>a<<<g>>>g8a<gg8g+8e8<e8g+>f+>a<<<g>>>g8a<ee8<<<e8.re8ere24f48f+e8d+8e8f+8.rf+8f+8r2r8>>>e2.g8b4.r8<c4.>g+8<c8c+2^c+8>g+8g8e8e1f+4.r8a4.r8b2<c2c+4.c2>b4.r8a+4.<c+8>a+8a2.a+8b2a2^a8g+4.<c2c+2e2^e8>f+1g+1c+2..r8<c2d2<e1>b1g+2<c2e4.>e2^e8c+1d+1e1^e1b1^b1r1r1r1r1r1r1r1r1r1r1r1r1r2..c8r8c8c8r8c8>b8<c8d4d8r4c4d4r2>e2.g8b4.r8<c4.>g+8<c8c+2^c+8>g+8g8e8e1f+4.r8a4.r8b2<c2c+4.c2>b4.r8a+4.<c+8>a+8a2.a+8b2a2^a8g+4.<c2c+2e2^e8>f+1g+1c+2..r8<c2d2e2r1r1r8>c+8d8d+8r4e2.r8e8e8e8r4;l16r1r1r2r8o3f+4a4r4a+4r8a+8b8r2.b8d4.r8f+2r8g2..f+2f2^f8e4r8f+4.r8g+4.r8a+4.r4<e.g32e8>>g+f+g<c8r>e8f8f+8<<e8e8b>>fg<c8r>e8f8f+8<<e8e8>>g+f+g<c8r>e8f8<a4.b4<c4c+4f+r8.c8r8dr8.dr8.c8c8r4g+r8g+8r8.>b8b8e8eeg8ggr8e8a+8a+8e8e8<er8e8r8.e8g8>>b8bb<e8re8e8r<e8e8er8.er8e8r8.d8d8dr8.d8d8dr>g4f+8g8a4b8<c8c+8d8>d8d4d8d<d>dd<c+dr8drd>dfa<dr>d<d>dd<c+d>d8d8d8c+dd8d<d>dd<c+d>>a8g8<ard8ard8f+rd8b8<f+rbrf+r>a+8<drg+r>a+r>a8<ar>a8<ar>a2<a2<d8>a8<c8>a8r4<d8.dr4d4r4>a8a4.r4<c+4.r8f+8r8f+4f+4f+8.f+r4f+4r4e8e4e8r4c+4c+8c+4f+8c+8r8>g1..r4ar<er>f+regr<e>f+rgrg+rar<er>f+regr<e>f+rgrg+r8e<er>f+regr<e>f+rgra4a8a8r8a8<d+8>a8b8.rb8b8r2r8<e4<e4.>b4<g+4.r8g+2..g+3r24g+8.rg+4.r2.>c+4f+4a4<e8r8>b4<f+8e8>g+4r4g+4r8g+4r4f+2>a+4.r4<e2..b4r8b8a4.r4g+4g+8<c4r4c+4.r8>>e4.r4<f+1g+1a+2..r8>c4..rd4..ra+8a+8a+8a+8a+8a+8a+8a+8a8a8a8a8a8a8a8a8g+8g+8g+8g+8<c8c8c8c8c+8c+8c+8>e4<c+8>g+8g8f+4f+8f+f+f+8f+8f+8ggg+4g+8g+g+g+8g+8r8g+8a8a8a8a8a8a8a8a8a8a8a8a8a8e8a8a+8b4b8b8b4b4b1r1r1r1r1r1r1r1r1r1r1r1r1r2..<<g8r8f8g8<c8c8>b8r8d4r4.>c8.rd8.r2re4<e4.>b4<g+4.r8g+2..g+3r24g+8.rg+4.r2.>c+4f+4a4<e8r8>b4<f+8e8>g+4r4g+4r8g+4r4f+2>a+4.r4<e2..b4r8b8a4.r4g+4g+8<c4r4c+4.r8>>e4.r4<f+1g+1a+2..r1r4.>>g+f+g<c8r>e8f8f+8r4rfg<c8r>e8f8f+8r4g+f+g<c8r2r8.e2d+dc+32c32>br8<e8e8d+32d32c+32c32>b32a+32ar8;l16r1r1o5d4r4.>d4c+4.r8a+4.a+4<f+4r4.>f+4f+4.r8a2^a8b2..f+2f2^f8b2.r8<d2>f2^f8<c+4r2.c+4r2.c+4r2r8>a4a8b8b8<c8c8c+8c+8r1r1r1r1r1r1r2..>g4f+8g8a4b8<c8c+8>>>d1^d1^d1..r4<<<f+2f2e2d2>f+1<f+1e4.e4.r4e4.r8>a+2<a4f+2^f+6r12d4.d2r8e4.e4.r4a+4.f2^f8g2f4g8c+8e4r1r1r2r8<b8.rb8br8.arg+rarb8r8b8b8r2..g+8g+g+r8g+8r8g+8r8g+8r8g+8r8g+8r8g+g+e8rg+g+8eg+r8g+8r8g+8r8g+8r8g+8r8g+8r4>a8<f+8c+8f+8c+8a8f+8a8f+8b8f+8e8g+8<c8>g+8<c8r8>e8r4.e8r8f+8r8a8r8<d8r2.>e8eer8g+8r8g+8r8g+8r8f+8r8f+8r8f+8b4r8g+8r8f+8r8g+8r8g+8e8g+8r8g+8e8g+8r8a8r8a8r8a8r8a8r8g+8r8e8r8b8r8b8r8e8r8e8r8e8r8e8>g4.r8f+2<g+4e8e8r8g+8r8e8r8d+8r8d+8r8f+8>b8<f+8>b8rbbrbb<c8ccr8ccr8g+8e8g+8r8g+8e8r8c+re8c+8eer8e8r8eer8f+8d+8f+8r8f+8r8d+d+e8cc>a8<cce8c8r8>aa<e8c8>a8<ccr8c8r8>a8<e8e8r8e8r8e8r8e8r8e8e8eer8e8e4>g4.r8a4.r8a+4.r8b2r1g+2..g+2g+2^g+8e2..g+2d4.>b4<a2f+2<d+2>g+2e4.e2e2e2^e6r1.r3a+4a+8a+8r8<c8>b8<c8>g4g8r4g4a4ref+g+ab<c+d+r4g+8g+g+r8g+8r8g+8r8g+8r8g+8r8g+8r8g+g+e8rg+g+8eg+r8g+8r8g+8r8g+8r8g+8r8g+8r4>a8<f+8c+8f+8c+8a8f+8a8f+8b8f+8e8g+8<c8>g+8<c8r8>e8r4.e8r8f+8r8a8r8<d8r2.>e8eer8g+8r8g+8r8g+8r8f+8r8f+8r8f+8b4r8g+8r8f+8r8g+8r8g+8e8g+8r8g+8e8g+8r8a8r8a8r8a8r8a8r8g+8r8e8r8b8r8b8r8e8r8e8r8e8r8e8r1r1r1.r8>>>g+8a8a+8r4<e2.r8e8e8e8r4;";


/////////////////////////////関数
//APUのポートから読み込み
unsigned char read_apu(unsigned char adr);
//APUのポートへ書き込み
void write_apu(unsigned char adr, unsigned char writedata);
//APUへデータ転送
void prog_write(unsigned int adr, unsigned char *writedata, int len);


//~~ユーザプログラム用関数/////////////////////////////

//~~APU-ATmega328p間転送系関数
//APU(ユーザープログラム動作中)のSRAMへ書き込み
void write_sram(unsigned int adr, unsigned char sram_writedata);
//APU(ユーザープログラム動作中)のDSPへ書き込み
void write_dsp(unsigned char adr, unsigned char dsp_writedata);
//APU(ユーザープログラム動作中)のSRAMから読み込み
unsigned char read_sram(unsigned int adr);
//APU(ユーザープログラム動作中)のDSPから読み込み
unsigned char read_dsp(unsigned int adr);
//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)
void write_sram_st(unsigned int adr, unsigned char *sram_writedata, unsigned int len);
//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)(プログラム用フラッシュメモリのデータをAPUへ連続転送する場合)
void write_sram_st_prg(unsigned int adr, unsigned char *sram_writedata, unsigned int len);

//~~DSPに書き込み系関数
//BRRベクタにサンプルベクタ登録
void etr_brr_vct(unsigned char smp_num, unsigned int start_vct_adr, unsigned int loop_vct_adr);
//ADSRエンベロープ登録
void etr_adsr_env(unsigned char ch, unsigned char attack, unsigned char decay, unsigned char sustain, unsigned char release);
//音程のセット
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);



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



//タイマー割り込み~~~~~~~~~~~~~~~~~~~~~~~~~
void tim1()//MML演奏用
{
  if(mml_time_cnt_en == 1){
    //MML演奏用時間カウント有効の時、カウント
    mml_time_ms++;//1msごとのカウント
    //mml_time = mml_time_ms * (9*8192.0 * tempo / (120.0 * 2000.0));//下式と等価
    mml_time = mml_time_ms * (3*64.0 * tempo / 625.0); //msをMMLカウンタ値に変換(トラックごとの演奏ずれをできるだけ抑制するため)
  }
 
}

//セットアップ
void setup()
{
  //デバッグメッセージ用
  Serial.begin(SERIALSPEED);//シリアル通信開始
  Serial.print("\r\npwr_on_ok\r\n");
  //APU関係
  PORTB = 0x0F;  //pin8~pin9_APU:A0~A1_output,pin10_APU:RD,pin11_APU:WE
  DDRB = 0x0F;  //pin8~pin9_APU:A0~A1_output,pin10_APU:RD,pin11_APU:WE
 
  digitalWrite(14, HIGH);//pin14_APU_reset
  pinMode(14, OUTPUT);  //pin14_APU_reset
 
  //SPC700プログラム書き込み
  prog_write(WRITE_ADR, progdata, PROGSIZE);
  //SPC700ユーザープログラム開始
  write_apu(3, START_ADR >> 8);
  write_apu(2, START_ADR & 0xFF);
  write_apu(1, 0x00);//SPC700ユーザープログラム開始モード
  write_apu(0, PROGSIZE + 1);//データ転送終了&ユーザープログラム開始
  Serial.print("Start user program\r\n");
  delay(100);//SPC700ユーザープログラム開始までの待ち時間
 
  //SPC700でユーザープログラムを実行しているため、
  //SRAMアドレス中の
  //$0000,$0001,$0004    (変数用領域)
  //$00F4 ~ $00F7        (APUポート領域)
  //$0100 ~ $01FF        (スタック領域)
  //$0200 ~ $0300        (ユーザープログラム領域)
  //これらのアドレスへのデータの書き込みは禁止です。
  //※SPC700のユーザープログラム側で、これらのアドレスへの書き込みのプロテクトはしていません。書き込まないように注意してください。

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

void loop()
{
  //①BRR圧縮されたデータをSRAM上に配置
  //BRRをAPUのSRAMへ配置します。
  write_sram_st_prg(0x1500, brrsample0, brrsample0_len);//配置開始アドレス,配列(ATmega328pのプログラムメモリ上)の先頭アドレス,配列サイズ
  write_sram_st_prg(0x7500, brrsample1, brrsample1_len);//配置開始アドレス,配列(ATmega328pのプログラムメモリ上)の先頭アドレス,配列サイズ
 
  //②BRRサンプルデータソースディレクトリアドレスに、BRR開始ブロックアドレスとループアドレスを登録(サンプル1開始ブロックアドレス(2Byte),サンプル1ループアドレス(2Byte),サンプル2開始ブロックアドレス(2Byte),サンプル2ループアドレス(2Byte),…,サンプルPCM_N開始ブロックアドレス(2Byte),サンプルPCM_Nループアドレス(2Byte)の順に配置)
  //BRRサンプルアドレス登録関数で開始アドレスとループアドレスを指定。
  etr_brr_vct(0, 0x1500, 0x1500+5346);//サンプル番号, 開始アドレス(2バイト), ループアドレス(2バイト)(ステレオサンプル数をNとするときで、ループ先サンプル点LNとするとき、ループアドレスは((LN+1)/2)*(9/8)となります。)
  etr_brr_vct(1, 0x7500, 0x7500+5445);//サンプル番号, 開始アドレス(2バイト), ループアドレス(2バイト)(ステレオサンプル数をNとするときで、ループ先サンプル点LNとするとき、ループアドレスは((LN+1)/2)*(9/8)となります。)

  //③BRRサンプルデータソースディレクトリレジスタ(SR_DIR($5D))にBRRサンプルデータソースディレクトリアドレスの上位8bitをセット(下位8bitは$00に固定されています。)
  write_dsp(SR_DIR, BRR_VCT >> 8);//SR_DIRに指定するソースディレクトリアドレスは上位8bitなので8ビット右へシフトする。
 
  //④BRRソース番号レジスタ(SRCN_X($X4))にサンプル番号PCM_Nを指定
  write_dsp(SRCN_X | CH0, 1);
  write_dsp(SRCN_X | CH1, 1);
  write_dsp(SRCN_X | CH2, 0);
  write_dsp(SRCN_X | CH3, 1);
  write_dsp(SRCN_X | CH4, 1);
  write_dsp(SRCN_X | CH5, 1);
  write_dsp(SRCN_X | CH6, 1);
  write_dsp(SRCN_X | CH7, 1);
 
  //⑤エンベロープ関係のレジスタ(ENV1_X($X5), ENV2_X(&X6), GAIN_X($X7))等の設定
  etr_adsr_env(CH0, 15, 3, 4, 15);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH1, 15, 2, 6, 0);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH2, 15, 1, 2, 15);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH3, 15, 3, 4, 15);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH4, 15, 3, 4, 15);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH5, 15, 7, 7, 1);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH6, 15, 7, 7, 1);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
  etr_adsr_env(CH7, 15, 7, 7, 1);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
 
  //⑥メイン音量レジスタ(MAIN_VOL_L($0C), MAIN_VOL_R($1C))の設定
  main_vel_pan_set(127,64);//音量, パン

  //未使用のレジスタの初期化
  write_dsp(ECHO_VOL_L, 0);//エコー左音量0
  write_dsp(ECHO_VOL_R, 0);//エコー右音量0
  write_dsp(ECHO_EN, 0);//エコー無効
  write_dsp(PTC_MD, 0);//ピッチモジュレーション無効
  write_dsp(NOISE_EN, 0);//ノイズ無効

  //一応キーオフもする
  write_dsp(KEY_OFF, 0xFF);//全チャンネルキーオフ
  write_dsp(KEY_OFF, 0x00);//全チャンネルのキーオフフラグをもどす

  //⑦DSP設定レジスタ(DSP_CONF($6C))の設定(DSPリセット処理も行う)
  write_dsp(DSP_CONF, DSP_RESET_EN | DSP_ECHO_DEN);//DSPリセット状態、エコー無効
  write_dsp(DSP_CONF, DSP_ECHO_DEN);//DSPリセット解除、エコー無効
 

  //音量初期設定(MML内でも記述可能です)
  //①チャンネル音量レジスタ(VOL_X_L($X0), VOL_X_R($X1))のセット
  vel_pan_set(CH0, 20, 64);//チャンネル0, 音量, パン
  vel_pan_set(CH1, 35, 64);//チャンネル1, 音量, パン
  vel_pan_set(CH2, 50, 60);//チャンネル2, 音量, パン
  vel_pan_set(CH3, 35, 64);//チャンネル3, 音量, パン
  vel_pan_set(CH4, 20, 58);//チャンネル4, 音量, パン
  vel_pan_set(CH5, 35, 68);//チャンネル5, 音量, パン
  vel_pan_set(CH6, 40, 64);//チャンネル6, 音量, パン
  vel_pan_set(CH7, 20, 70);//チャンネル7, 音量, パン
 
  Serial.print("DSP setup ok\r\n");
  while(1)
  {
    //MML演奏
    mml_play(mml_data, mml_data_len);
  }
}

//APUのポートから読み込み
unsigned char read_apu(unsigned char adr)
{
  unsigned char readdata;

  DDRB = 0x0F;//arduino_data_pin_input
  DDRD &= ~0xFC;//arduino_data_pin_input

  PORTB = (~0x03 & PORTB) | (adr & 0x03);   //APU_address_write


  digitalWrite(10, LOW);//pin10_APU_RD
  delayMicroseconds(1);
  readdata = ((PINB & 0x30) >> 4) | (PIND & 0xFC);
  digitalWrite(10, HIGH);//pin10_APU_RD
  return readdata;
}

//APUのポートへ書き込み
void write_apu(unsigned char adr, unsigned char writedata)
{
  DDRB = 0x3F;//arduino_data_pin_output
  DDRD |= 0xFC;//arduino_data_pin_output

  PORTB = (~0x03 & PORTB) | (adr & 0x03);   //APU_address_write

  PORTD = (~0xFC & PORTD) | (writedata & 0xFC);   //APU_data_write
  PORTB = (~0x30 & PORTB) | (((writedata & 0x03) << 4) & 0x30);   //APU_data_write

  digitalWrite(11, LOW);//pin11_APU_WE
  delayMicroseconds(1);
  digitalWrite(11, HIGH);//pin11_APU_WE

}

//APUへデータ転送
void prog_write(unsigned int adr, unsigned char *writedata, int len)
{
  //APUリセット
  digitalWrite(14, LOW);
  delay(10);
  digitalWrite(14, HIGH);

  while(!((read_apu(0) == 0xAA) && (read_apu(1) == 0xBB))){
    //APUが初期化を完了するまで待機
    static int i_t_c=0;
    delayMicroseconds(1);
    if(i_t_c>=1000){
      Serial.print("Time out : APU not found : PORT0=0xAA and PORT1=0xBB did not return\r\n");
      delay(1000);
      asm volatile ("  jmp 0");//タイムアウトしたら(APUへの読み書き失敗)Arduinoをリセットする。
    }
    i_t_c++;
  }
  write_apu(3, adr >> 8);
  write_apu(2, adr & 0xFF);
  write_apu(1, 1);//APUユーザープログラム転送モード
  write_apu(0, 0xCC);
  while(read_apu(0) != 0xCC){
    //APUが転送準備を完了するまで待機
    static int i_t_c=0;
    delayMicroseconds(1);
    if(i_t_c>=1000){
      Serial.print("Time out : PORT0=0xCC did not return\r\n");
      delay(1000);
      asm volatile ("  jmp 0");//タイムアウトしたら(APUへの読み書き失敗)Arduinoをリセットする。
    }
    i_t_c++;
  }
  for(unsigned int i = 0;i < len;i++)
  {
    write_apu(1, writedata[i]);
    write_apu(0, i & 0xFF);
    while(read_apu(0) != (i & 0xFF)){
      //APUが転送データ1バイトを書き込むまで待機
      static int i_t_c=0;
      delayMicroseconds(1);
      if(i_t_c>=1000){
        Serial.print("Time out : Failed to transfer the user program\r\n");
        delay(1000);
        asm volatile ("  jmp 0");//タイムアウトしたら(APUへの読み書き失敗)Arduinoをリセットする。
      }
      i_t_c++;
    }
  }
}

//~~ユーザプログラム用関数/////////////////////////////

//APU(ユーザープログラム動作中)のSRAMへ書き込み
void write_sram(unsigned int adr, unsigned char sram_writedata)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAM_への書き込み手順
  //1.アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.データをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  unsigned char temp_port0;
  int i = 0;
  //Serial.print("apu_port0_read:0x");//デバッグ用
  //Serial.print(read_apu(0), HEX);//デバッグ用
  //Serial.print("\r\n");//デバッグ用
  write_apu(2, adr & 0xFF);
  write_apu(3, (adr>>8) & 0xFF);
  write_apu(1, sram_writedata);
  temp_port0 = read_apu(0);
  write_apu(0, (temp_port0 ^ B00000001) & B11001111);
 
  for(i = 0;i < 100;i++){
    //Serial.print("apu_port0_read:0x");//デバッグ用
    //Serial.print(read_apu(0), HEX);//デバッグ用
    //Serial.print("\r\n");//デバッグ用
    if(((read_apu(0) ^ temp_port0) & B00000100) != 0){
      return;
    }
  }
  timeout = 1;
  Serial.print("Timeout : write_sram_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
}
//APU(ユーザープログラム動作中)のDSPへ書き込み
void write_dsp(unsigned char adr, unsigned char dsp_writedata)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //DSP_への書き込み手順
  //1.アドレス0~7bitをAPUのポート2に書き込む
  //2.データをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=H(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  unsigned char temp_port0;
  int i = 0;
  write_apu(2, adr);
  write_apu(1, dsp_writedata);
  temp_port0 = read_apu(0);
  write_apu(0, ((temp_port0 ^ B00000001) | B00100000) & B11101111);
 
  for(i = 0;i < 100;i++){
    if(((read_apu(0) ^ temp_port0) & B00000100) != 0){
      return;
    }
  }
  timeout = 1;
  Serial.print("Timeout : write_dsp_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
}

//APU(ユーザープログラム動作中)のSRAMから読み込み
unsigned char read_sram(unsigned int adr)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAMから読み込み手順
  //1.アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit1(読み込み要求)を反転させて、APUのポート0に書き込む
  //3.APUのポート0を読み取り、bit3(読み込み完了通知)が反転するまで待つ
  //4.APUのポート1(データ)のデータを読み取る
  unsigned char temp_port0;
  int i = 0;
  write_apu(2, adr & 0xFF);
  write_apu(3, (adr>>8) & 0xFF);
  temp_port0 = read_apu(0);
  write_apu(0, (temp_port0 ^ B00000010) & B11001111);

  for(i = 0;i < 100;i++){
    if(((read_apu(0) ^ temp_port0) & B00001000) != 0){
      return read_apu(1);
    }
  }
  timeout = 1;
  Serial.print("Timeout : read_sram_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
  return 0;
}

//APU(ユーザープログラム動作中)のDSPから読み込み
unsigned char read_dsp(unsigned int adr)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //DSPから読み込み手順
  //1.アドレス0~7bitをAPUのポート2に書き込む
  //2.APUのポート0を読み取り、bit5=H(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit1(読み込み要求)を反転させて、APUのポート0に書き込む
  //3.APUのポート0を読み取り、bit3(読み込み完了通知)が反転するまで待つ
  //4.APUのポート1(データ)のデータを読み取る
  unsigned char temp_port0;
  int i = 0;
  write_apu(2, adr);
  temp_port0 = read_apu(0);
 
  write_apu(0, ((temp_port0 ^ B00000010) | B00100000) & B11101111);
  for(i = 0;i < 100;i++){
    if(((read_apu(0) ^ temp_port0) & B00001000) != 0){
      return read_apu(1);
    }
  }
  timeout = 1;
  Serial.print("Timeout : read_dsp_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
  return 0;
}

//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)
void write_sram_st(unsigned int adr, unsigned char *sram_writedata, unsigned int len)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAM_への連続書き込み手順
  //1.開始アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.最初のデータをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ

  //5.次のデータをAPUのポート1に書き込む
  //6.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント有効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //7.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  //8.「5.次のデータをAPUのポート1に書き込む」へ行く。終了する場合はそのまま終了

  unsigned char temp_port0;
  int i = 0;
  write_sram(adr,sram_writedata[0]);//0番目のデータを書いてもまだオートインクリメントはしない。
  for(unsigned int j = 0;j < len;j++){//もういちど0番目のデータを書いた後、アドレスをオートインクリメントする。
    write_apu(1, sram_writedata[j]);
    temp_port0 = read_apu(0);
    write_apu(0, ((temp_port0 ^ B00000001) | B00010000) & B11011111);
    for(i = 0;i < 200;i++){
      if(((read_apu(0) ^ temp_port0) & B00000100) != 0){
        break;
      }
    }
    if(i >= 100){
      timeout = 1;//タイムアウト検出用
      return;
    }
  }
 
}

//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)(プログラム用フラッシュメモリのデータをAPUへ連続転送する場合)
void write_sram_st_prg(unsigned int adr, unsigned char *sram_writedata, unsigned int len)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAM_への連続書き込み手順
  //1.開始アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.最初のデータをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ

  //5.次のデータをAPUのポート1に書き込む
  //6.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント有効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //7.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  //8.「5.次のデータをAPUのポート1に書き込む」へ行く。終了する場合はそのまま終了

  unsigned char temp_port0;
  int i = 0;
  write_sram(adr,pgm_read_byte_near(sram_writedata));//0番目のデータを書いてもまだオートインクリメントはしない。
  for(unsigned int j = 0;j < len;j++){//もういちど0番目のデータを書いた後、アドレスをオートインクリメントする。
    write_apu(1, pgm_read_byte_near(sram_writedata + j));
    temp_port0 = read_apu(0);
    write_apu(0, ((temp_port0 ^ B00000001) | B00010000) & B11011111);
    for(i = 0;i < 200;i++){
      if(((read_apu(0) ^ temp_port0) & B00000100) != 0){
        break;
      }
    }
    if(i >= 100){
      timeout = 1;//タイムアウト検出用
      return;
    }
  }
 
}

//BRRベクタにサンプルベクタ登録
void etr_brr_vct(unsigned char smp_num, unsigned int start_vct_adr, unsigned int loop_vct_adr)
{
  //BRRベクタ登録関数
  //BRRサンプルデータ開始アドレスとループアドレス、サンプル番号を指定すると、BRRベクタに登録します。
 
  //sample0_start_Lbyte, sample0_start_Hbyte, sample0_loop_Lbyte, sample0_loop_Hbyte
  write_sram(BRR_VCT + (smp_num * 4), start_vct_adr & 0xFF);
  write_sram(BRR_VCT + (smp_num * 4) + 1, (start_vct_adr >> 8) & 0xFF);
  write_sram(BRR_VCT + (smp_num * 4) + 2, loop_vct_adr & 0xFF);
  write_sram(BRR_VCT + (smp_num * 4) + 3, (loop_vct_adr >> 8) & 0xFF);
}

//ADSRエンベロープ登録
void etr_adsr_env(unsigned char ch, unsigned char attack, unsigned char decay, unsigned char sustain, unsigned char release)
{
  //エンベロープ登録関数(この関数を実行すると自動的にADSRエンベロープが有効になります。)
  //エンベロープ関係のレジスタ(ENV1_X($X5), ENV2_X(&X6), GAIN_X($X7))等の設定
 
  write_dsp(ENV1_X | ch, DSP_ENV_ADSR_EN | ((0x07 & decay) << 4) | (0x0F & attack));
  write_dsp(ENV2_X | ch, ((0x07 & sustain) << 5) | (0x1F & release));
 
}


//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum)
{
  //音程レジスタ(PTC_X_L($X2), PTC_X_H($X3))のセット
  //MIDIのノート番号からDSPのピッチを求めるテーブルより、ピッチを求めてチャンネルchにセット
  write_dsp(PTC_X_L | ch, noteFreq[notenum] & 0xFF);
  write_dsp(PTC_X_H | ch,(noteFreq[notenum] >> 8) & 0x3F);
}

//メインの音量とパンのセット
void main_vel_pan_set(unsigned char vel,unsigned char pan)
{
  //音量, パン
  //メイン音量レジスタ(MAIN_VOL_L($0C), MAIN_VOL_R($1C))の設定
  //音量(0~127)とパン(0~127)を乗算して、128で割る(最大出力は127*127/128=126)
  //パンは、64で中央、1で左端、127で右端  (0は1として処理)
  if(pan == 0){
    pan = 1;
  }
  write_dsp(MAIN_VOL_L, (unsigned char)(((unsigned int)vel * (128 - pan)) >> 7));
  write_dsp(MAIN_VOL_R, (unsigned char)(((unsigned int)vel * pan) >> 7));
}

//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel,unsigned char pan)
{
  //チャンネル, 音量, パン
  //チャンネル音量レジスタ(VOL_X_L($X0), VOL_X_R($X1))のセット
  //音量(0~127)とパン(0~127)を乗算して、128で割る(最大出力は127*127/128=126)
  //パンは、64で中央、1で左端、127で右端  (0は1として処理)
  if(pan == 0){
    pan = 1;
  }
  write_dsp(VOL_X_L | ch, (unsigned char)(((unsigned int)vel * (128 - pan)) >> 7));
  write_dsp(VOL_X_R | ch, (unsigned char)(((unsigned int)vel * pan) >> 7));
}

//チャンネルノートオン
void note_on(unsigned char ch)
{
  //チャンネル(0~8を左4ビットシフトしたもの)
  //キーオンレジスタ(KEY_ON($4C))のセット
  write_dsp(KEY_OFF, 0);//ふたたびノートオフできるようにする
  write_dsp(KEY_ON, 1 << (ch >> 4));
}

//チャンネルノートオフ
void note_off(unsigned char ch)
{
  //チャンネル(0~8を左4ビットシフトしたもの)
  //キーオフレジスタ(KEY_OFF($5C))のセット
  write_dsp(KEY_OFF, 1 << (ch >> 4));
}



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

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

  int mml_com[4] = {0, 0, 0, 0 };//mmlコマンド取得用[コード, 数値1, 時間, 数値2(音長表現のドットの数)]
 
  int mml_st[MML_MAX_TR];//mml演奏状態(0でmml読み取り終了、1でmml読み取り中)、チャンネルごとに保存
  char mml_tie[MML_MAX_TR];//タイ有効無効フラグ[^]
  char mml_oct[MML_MAX_TR];//オクターブ[o]
  char mml_def_dly[MML_MAX_TR];//デフォルト音調[l]
  char mml_pan[MML_MAX_TR];//パン[p]
  char mml_vel[MML_MAX_TR];//ベロシティ[v]
  double mml_next_tim[MML_MAX_TR];//次のMMLコマンド実行時間(絶対時間)
  int mml_st_sum = 0;//トラック演奏状態チェック用(フラグを加算して0になったらすべてのトラックの演奏が終了)
 
  ////////////////トラック数の取得と各トラック先頭位置の取得//////
  unsigned int tr_pos[MML_MAX_TR];//トラック先頭位置保存用(先頭位置)
  int tr_num = get_mml_tr_pos(mml_data, mml_data_len, MML_MAX_TR, tr_pos);//各トラック先頭位置をtr_posに入れ、トラック数を返す

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

    mml_st_sum = 0;//トラック演奏状態チェック用初期化
    //トラックごと処理
   
    for(int mml_st_tr = 0;mml_st_tr < tr_num; mml_st_tr++){
      //Serial.print("mml_st[mml_st_tr]:");//デバッグ用
      //Serial.print(mml_st[mml_st_tr]);//デバッグ用
      //Serial.print("\r\n");//デバッグ用
      if(mml_st[mml_st_tr] == 0){//演奏が終了したトラックは無視
        continue;
      }
     
      //Serial.print("mml_time:");//デバッグ用
      //Serial.print(mml_time);//デバッグ用
      //Serial.print("mml_next_tim[mml_st_tr]:");//デバッグ用
      //Serial.print(mml_next_tim[mml_st_tr]);//デバッグ用
      //Serial.print("\r\n");//デバッグ用
     
      if((unsigned long)mml_next_tim[mml_st_tr] <= (unsigned long)mml_time){ //次回読み取り時間カウンタが時間カウンタの値以上になったらMMLコードを読み取る。
        ////////////////////////////
       
        //mmlコマンド取得(pos自動インクリメント)
        get_mml_com(play_data, len, (tr_pos + mml_st_tr), mml_com);//返しは、[コード, 数値1, 時間, 数値2(音長表現のドットの数)]の配列ポインタ
        if(mml_com[2] == -2){//MMLの音調が省略されていたらデフォルト音長をセット
          mml_com[2] = mml_def_dly[mml_st_tr];
        }
        //MMLコマンド表示(多少不安定になります)//デバッグ用
        /*Serial.print("ch:");
        Serial.print(mml_st_tr);
        Serial.print("_pos:");
        Serial.print(tr_pos[mml_st_tr]);
        Serial.print("_");
        Serial.write(mml_com[0]);
        Serial.print(mml_com[1]);
        Serial.print("_dlytim:");
        Serial.print(mml_com[2]);
        Serial.print("_dot:");
        Serial.print(mml_com[3]);
        Serial.print("\r\n");*/

        switch(mml_com[0]){
          case 'n'://ノートオン
            if(mml_tie[mml_st_tr] == 0){//タイ無効の時音を鳴らす
              ptc_set(mml_st_tr << 4, ((mml_oct[mml_st_tr] + brr_oct[mml_st_tr]) * 12) + mml_com[1]);//チャンネル, 音程
              note_on(mml_st_tr << 4);//ノートオン
            }
            mml_tie[mml_st_tr] = 0;//タイ無効
            break;
          case 'r'://ノートオフ
            note_off(mml_st_tr << 4);
            break;
          case 'o'://オクターブセット
            mml_oct[mml_st_tr] = mml_com[1];
            break;
          case '>'://オクターブ下げ
            mml_oct[mml_st_tr]--;
            break;
          case '<'://オクターブ上げ
            mml_oct[mml_st_tr]++;
            break;
          case '^'://タイ
            mml_tie[mml_st_tr] = 1;//タイ有効
            break;
          case 'l'://デフォルト音長
            mml_def_dly[mml_st_tr] = mml_com[1];//音長
            break;
          case 'v'://ベロシティ
            mml_vel[mml_st_tr] = mml_com[1];
            vel_pan_set(mml_st_tr << 4, mml_vel[mml_st_tr], mml_pan[mml_st_tr]);//チャンネル, 音量, パン
            break;
          case 'p'://パン
            mml_pan[mml_st_tr] = mml_com[1];
            vel_pan_set(mml_st_tr << 4, mml_vel[mml_st_tr], mml_pan[mml_st_tr]);//チャンネル, 音量, パン
            break;
          case 't'://テンポ
            tempo = mml_com[1];
            break;
          case ';'://トラック終端
            mml_st[mml_st_tr] = 0;//現在のトラックは演奏終了
            break;
          default:
            break;
        }

        //時間計算
        if(mml_com[2] == 0){
          //待ち時間なし
        }else{
          //delay(500);
          //待ち時間計算(ms)
          double dly_tim_dot_sum = 0;
          for(int d_time = 0; d_time <= mml_com[3];d_time++)//mmlのドットの数分ディレイを入れる
          {
            dly_tim_dot_sum += (double)(9.0*8192.0 / (mml_com[2]*pow(2, d_time)));
            //delay((int)((2000.0 / (mml_com[2]*pow(2, d_time))) * (120.0 / tempo)));
          }
          //次回のMMLコマンド実行時間を加算する
          mml_next_tim[mml_st_tr] += dly_tim_dot_sum;
     
        }
        ////////////////////////////
      }
     
      mml_st_sum += mml_st[mml_st_tr];//トラック演奏状態チェック用(フラグを加算して0になったらすべてのトラックの演奏が終了)
        //Serial.print("mml_st_sum:");//デバッグ用
        //Serial.print(mml_st_sum);//デバッグ用
        //Serial.print("\r\n");//デバッグ用
    }
    //トラック演奏状態チェック
    if(mml_st_sum == 0){//すべてのトラックが演奏終了のとき、mml演奏関数終了
      break;//mml演奏関数終了
    }
  }
  mml_time_cnt_en = 0;//MML演奏用時間カウント_停止
 
}
//簡易mmlデータ1バイト取得
int get_mml_data(unsigned char *mml_data, unsigned int len, unsigned int pos)
{
  //posは0~(len - 1)までを指定
  //データ範囲外で -1 を返す。
 
  if(pos >= len){
    return -1;
  }
  //return mml_data[pos];    //MMLデータをSRAMに配置した時
  return pgm_read_byte_near(mml_data + pos);   //MMLデータをPROGMEMに配置した時

}

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

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

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

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

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

  //数値読み取り
  while((mml_data_byte >= '0') && (mml_data_byte <= '9')){
    //ret_num += (mml_data_byte - '0')*pow(10, keta);//数字と桁数の乗算値を加算
    ret_num = ret_num * 10 + (mml_data_byte - '0');

    keta++;
    mml_data_byte = get_mml_data(mml_data, len, pos_temp + keta);
  }
 
  //ドットの数を数える
  mml_data_byte = get_mml_data(mml_data, len, pos_temp + keta);
  while(mml_data_byte == '.'){
    ret_dp_num++;
    mml_data_byte = get_mml_data(mml_data, len, pos_temp + keta + ret_dp_num);
  }
 
  *pos += keta + ret_dp_num;//現在の読み取り位置に読み取った数分、加算
  *dp_num = ret_dp_num;//ドットの数書き換え
  return ret_num;
}

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

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

}

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

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







・MIDIを受信する

最後に、MIDIを受信するプログラムを追加して完成です。


・回路図
MIDIを受信するための回路を追加しました。
MIDI受信部分の回路は、「MIDI 1.0 電気的仕様改訂 (CA-033)」の回路を参考にしました。

MIDI規格推奨応用事例(RP)/MIDI規格追記事項(CA)


APU内のDSPのバージョンは、「S-DSP」や「S-DSP A」などの種類がありますが、どちらも同じ回路で演奏できます。

[2022/5/30] 追記 : 回路図中のフォトカプラの記号にミスがあったので、修正しました。



※1個の場合、APUの「PA6_CS1」はArduinoに接続せず、5V直結でもかまいません。


2個以上のAPUを使う場合の回路

[2022/5/30 追記] : 回路図中のフォトカプラの記号にミスがあったので、修正しました。






・プログラム
今回作成したMIDIを受信するプログラムは、音程によってBRRサンプルを変更しています。

デモで使用したBRRサンプルでは、ピッチが若干ずれているので気になる方は、音程テーブルを修正して利用してください。

また、APUを複数接続して発音チャンネルを増やせるようにしました。
エコーの有効化もできます。


//SPC700でMIDI演奏プログラム
//©oy
//https://oykenkyu.blogspot.com/2021/10/spc700-midi.html


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


#define DB_MESS 0 //デバッグメッセージ有効無効
#define AUTO_RESET 0 //APUユーザプログラム書き込み失敗時にリセット有効無効


#define XTAL 16000000//水晶振動子の周波数(MIDI受信速度に影響)
//プログラムの変更をせずに、Arduinoの水晶振動子を19.660MHzに変更すると、38400bpsで受信できます。

//#define SERIALSPEED_DEF 38400 //UARTのボーレート
#define SERIALSPEED_DEF 31250//ボーレート(MIDIは31250bps)
#define SERIALSPEED ((long)((long long)SERIALSPEED_DEF * 16000000.0 / XTAL))

//受信バッファサイズを64バイトから256バイトにするとMIDI受信バッファあふれを抑制できるかも
//[C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\HardwareSerial.h]内の「#define SERIAL_RX_BUFFER_SIZE 64」を「#define SERIAL_RX_BUFFER_SIZE 256」にする

//SNES_APUの数(1基で8チャンネル分発音できます)
#define APU_NUM 1
//MIDI演奏最大トラック数(SNESのAPUは1器で8チャンネル分発音できます。)
#define MIDI_MAX_TR 8

//SNES_APUの~csピン番号(連番)
#define APU_CS_PIN 16//16,17,18,19

//ユーザープログラムのサイズ(Byte)
#define PROGSIZE 172

//SPC700プログラム書き始めアドレス
#define WRITE_ADR 0x0200

//SPC700プログラム開始アドレス
#define START_ADR 0x0200

//BRRサンプルベクタテーブルアドレス
//このアドレスは、BRRサンプルベクタの場所を示しています。
#define BRR_VCT 0x0700

//エコー用メモリ領域の先頭アドレス(※エコーバッファが小さいと、ユーザプログラム領域やBBRの書き換えが発生する可能性があります。)
#define ECHO_ADR 0x8700


//DSPレジスタ名
#define VOL_X_L 0x00
#define VOL_X_R 0x01
#define PTC_X_L 0x02
#define PTC_X_H 0x03
#define SRCN_X 0x04
#define ENV1_X 0x05
#define ENV2_X 0x06
#define GAIN_X 0x07
#define DSP_ENVX_X 0x08
#define DSP_OUTX_X 0x09
#define MAIN_VOL_L 0x0C
#define MAIN_VOL_R 0x1C
#define ECHO_VOL_L 0x2C
#define ECHO_VOL_R 0x3C
#define KEY_ON 0x4C
#define KEY_OFF 0x5C
#define DSP_CONF 0x6C
#define END_SMPL 0x7C
#define ECHO_FB 0x0D
#define PTC_MD 0x2D
#define NOISE_EN 0x3D
#define ECHO_EN 0x4D
#define SR_DIR 0x5D
#define ECHO_BUF_ADR 0x6D
#define ECHO_DLY 0x7D
#define ECHO_FIR_X 0x0F

//DSPチャンネル名
#define CH0 0x00
#define CH1 0x10
#define CH2 0x20
#define CH3 0x30
#define CH4 0x40
#define CH5 0x50
#define CH6 0x60
#define CH7 0x70

//DSPレジスタのビット
#define DSP_RESET_EN 0x80
#define DSP_MUTE_EN 0x40
#define DSP_ECHO_DEN 0x20
#define DSP_ENV_ADSR_EN 0x80



char timeout = 0;//APU書き込みタイムアウト検出用

char echo_en = 1;//エコー有効
//エコー遅延量(4bitで指定、指定値*16msの遅延が得られます。ただし、遅延量が大きいとエコーバッファも大きくしなければいけません。エコーバッファがBRRサンプルデータやユーザプログラムを破壊しないように注意してください。)
char echo_delay = 12;
char echo_fg = 64;//エコーフィードバックゲイン(大きくし過ぎると発振します。)
char echo_vel_l = 16;//エコー左音量
char echo_vel_r = 16;//エコー右音量
char echo_fir[8] = {127, 0, 0, 0, 0, 0, 0, 0};//エコーFIRフィルタ係数

//デフォルトオクターブ[sample0,sample1,sample2,…,]
//増やすとオクターブが高くなります。(出力オクターブ = brr_oct + (mmlのオクターブ))
int brr_oct[4] = {3, 2, 1, 0};
int brr_tone[4] = {-2, 3, -4, 1};

//MIDI受信用
char midi_main_vel[16];//メインベロシティ(midiチャンネルが配列要素順)
char midi_pan[16];//パン(midiチャンネルが配列要素順)
char midi_vel[MIDI_MAX_TR];//ベロシティ(APUチャンネルが配列要素順)

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()で使用
int comp_read_buf=0;         //midi_read()で使用
unsigned char running_ch=0x90;//ランニングステータスチャンネル

unsigned char midinote_tn[16]; //MIDIノートオン音程保存
unsigned char midinote_ch[16]; //MIDIノートオンチャンネル保存
unsigned char midinote[16];    //MIDIノートオン判定保存
unsigned int midinote_off[16]; //MIDIノートオフ時配列計算用保存
unsigned char midiprog[16];    //MIDIプログラムチェンジ保存
unsigned char sas_en = 0;      //サスティン有効フラグ


//ユーザープログラム
//SRAM・DSPとマイコン間データ転送プログラム
//プログラム詳細
//https://oykenkyu.blogspot.com/2021/10/spc700-midi.html
unsigned char progdata[PROGSIZE] = {
  0x8F, 0x00, 0x00, 0x8F, 0x00, 0x01, 0x8F, 0x00, 0xF5, 0x8F, 0x00, 0xF6, 0x8F, 0x00, 0xF7, 0xE4,
  0xF4, 0x28, 0x3F, 0xC4, 0x04, 0xFA, 0x04, 0xF4, 0xE4, 0xF4, 0x44, 0x04, 0x28, 0x01, 0xD0, 0x0A,
  0xE4, 0xF4, 0x44, 0x04, 0x28, 0x02, 0xD0, 0x43, 0x2F, 0xEE, 0xE4, 0xF4, 0x28, 0x10, 0xF0, 0x0A,
  0x18, 0x10, 0x04, 0x3F, 0x50, 0x02, 0x3A, 0x00, 0x2F, 0x0A, 0x38, 0xEF, 0x04, 0xBA, 0xF6, 0xDA,
  0x00, 0x3F, 0x50, 0x02, 0x58, 0x05, 0x04, 0xBA, 0x00, 0xDA, 0xF6, 0xFA, 0x04, 0xF4, 0x2F, 0xC8,
  0xE4, 0xF4, 0x28, 0x20, 0xF0, 0x0B, 0x18, 0x20, 0x04, 0xFA, 0x00, 0xF2, 0xFA, 0xF5, 0xF3, 0x2F,
  0x09, 0x38, 0xDF, 0x04, 0x8D, 0x00, 0xE4, 0xF5, 0xD7, 0x00, 0x6F, 0xE4, 0xF4, 0x28, 0x10, 0xF0,
  0x0A, 0x18, 0x10, 0x04, 0x3F, 0x91, 0x02, 0x3A, 0x00, 0x2F, 0x0A, 0x38, 0xEF, 0x04, 0xBA, 0xF6,
  0xDA, 0x00, 0x3F, 0x91, 0x02, 0x58, 0x0A, 0x04, 0xBA, 0x00, 0xDA, 0xF6, 0xFA, 0x04, 0xF4, 0x2F,
  0x87, 0xE4, 0xF4, 0x28, 0x20, 0xF0, 0x0B, 0x18, 0x20, 0x04, 0xFA, 0x00, 0xF2, 0xFA, 0xF3, 0xF5,
  0x2F, 0x09, 0x38, 0xDF, 0x04, 0x8D, 0x00, 0xF7, 0x00, 0xC4, 0xF5, 0x6F
};


//BRRサンプル0
//piano1
//著作権の関係上ここには同胞していません。
//演奏デモでは「https://www.smwcentral.net/?p=section&a=details&id=17248」の"piano1 ---g2.brr"を使用
//上記brrデータの先頭2バイトは、ループ先アドレスを示すので、3バイト目以降を下の配列に入れてください。
unsigned int brrsample0_len = 5634;//BRRサンプル0サイズ
const unsigned char PROGMEM brrsample0[5634] = {
};


//BRRサンプル1
//piano1
//著作権の関係上ここには同胞していません。
//演奏デモでは「https://www.smwcentral.net/?p=section&a=details&id=17248」の"piano2 g#2-d#3.brr"を使用
//上記brrデータの先頭2バイトは、ループ先アドレスを示すので、3バイト目以降を下の配列に入れてください。
unsigned int brrsample1_len = 5634;//BRRサンプル1サイズ
const unsigned char brrsample1[5634] PROGMEM = {
};

//BRRサンプル2
//piano
//著作権の関係上ここには同胞していません。
//演奏デモでは「https://www.smwcentral.net/?p=section&a=details&id=17248」の"piano5 g#4-b4.brr"を使用
//上記brrデータの先頭2バイトは、ループ先アドレスを示すので、3バイト目以降を下の配列に入れてください。
unsigned int brrsample2_len = 5634;//BRRサンプル2サイズ
const unsigned char PROGMEM brrsample2[5634] = {
};


//BRRサンプル3
//piano1
//著作権の関係上ここには同胞していません。
//演奏デモでは「https://www.smwcentral.net/?p=section&a=details&id=17248」の"piano6 c5-f5.brr"を使用
//上記brrデータの先頭2バイトは、ループ先アドレスを示すので、3バイト目以降を下の配列に入れてください。
unsigned int brrsample3_len = 5634;//BRRサンプル3サイズ
const unsigned char PROGMEM brrsample3[5634] = {
};



//MIDIのノート番号からDSPのピッチを求めるテーブル(BRRサンプリング周波数32000Hzで440Hz(A)の音程の楽器を録音)
const unsigned int noteFreq[128] = {
  //A, A#, B, C, C#, D, D#, E, F, F#, G, G#
  /**/ /**/ /**/ 76, 81, 85, 91, 96, 102, 108, 114, 121,
  128, 136, 144, 152, 161, 171, 181, 192, 203, 215, 228, 242,
  256, 271, 287, 304, 323, 342, 362, 384, 406, 431, 456, 483,
  512, 542, 575, 609, 645, 683, 724, 767, 813, 861, 912, 967,
  1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933,
  2048, 2170, 2299, 2435, 2580, 2734, 2896, 3069, 3251, 3444, 3649, 3866,
  4096, 4340, 4598, 4871, 5161, 5468, 5793, 6137, 6502, 6889, 7298, 7732,
  8192, 8679, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596, 15464,
  16383, 8679, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596, 15464,
  16383, 8679, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596, 15464,
  16383, 8679, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596
};


/////////////////////////////関数
//APUのポートから読み込み
unsigned char read_apu(unsigned char adr, unsigned char cs);
//APUのポートへ書き込み
void write_apu(unsigned char adr, unsigned char writedata, unsigned char cs);
//APUへデータ転送
void prog_write(unsigned int adr, unsigned char *writedata, int len, unsigned char cs);


//~~ユーザプログラム用関数/////////////////////////////

//~~APU-ATmega328p間転送系関数
//APU(ユーザープログラム動作中)のSRAMへ書き込み
void write_sram(unsigned int adr, unsigned char sram_writedata, unsigned char cs);
//APU(ユーザープログラム動作中)のDSPへ書き込み
void write_dsp(unsigned char adr, unsigned char dsp_writedata, unsigned char cs);
//APU(ユーザープログラム動作中)のSRAMから読み込み
unsigned char read_sram(unsigned int adr, unsigned char cs);
//APU(ユーザープログラム動作中)のDSPから読み込み
unsigned char read_dsp(unsigned int adr, unsigned char cs);
//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)
void write_sram_st(unsigned int adr, unsigned char *sram_writedata, unsigned int len, unsigned char cs);
//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)(プログラム用フラッシュメモリのデータをAPUへ連続転送する場合)
void write_sram_st_prg(unsigned int adr, unsigned char *sram_writedata, unsigned int len, unsigned char cs);

//~~DSPに書き込み系関数
//BRRベクタにサンプルベクタ登録
void etr_brr_vct(unsigned char smp_num, unsigned int start_vct_adr, unsigned int loop_vct_adr, unsigned char cs);
//ADSRエンベロープ登録
void etr_adsr_env(unsigned char ch, unsigned char attack, unsigned char decay, unsigned char sustain, unsigned char release, unsigned char cs);
//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum, unsigned char cs);
//メインの音量とパンのセット
void main_vel_pan_set(unsigned char vel,unsigned char pan, unsigned char cs);
//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel,unsigned char pan, unsigned char cs);
//チャンネルノートオン
void note_on(unsigned char ch, unsigned char cs);
//チャンネルノートオフ
void note_off(unsigned char ch, unsigned char cs);


//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);//シリアル通信開始
  Serial.print("\r\npwr_on_ok\r\n");
  //APU関係
  PORTB = 0x0F;  //pin8~pin9_APU:A0~A1_output,pin10_APU:RD,pin11_APU:WE
  DDRB = 0x0F;  //pin8~pin9_APU:A0~A1_output,pin10_APU:RD,pin11_APU:WE
 
 
  pinMode(14, OUTPUT);  //pin14_APU_reset
  digitalWrite(14, HIGH);//pin14_APU_reset

  //APUリセット
  digitalWrite(14, LOW);
  delay(10);
  digitalWrite(14, HIGH);
 
  for(int apu_cs = 0; apu_cs < APU_NUM;apu_cs++)
  {
    timeout = 0;
    pinMode(APU_CS_PIN + apu_cs, OUTPUT);  //pin16,17,18..._APU_cs
    digitalWrite(APU_CS_PIN + apu_cs, HIGH);//pin16,17,18..._APU_cs
   
    //SPC700プログラム書き込み
    prog_write(WRITE_ADR, progdata, PROGSIZE, apu_cs);
    if(timeout != 0){
      Serial.print("APU");
      Serial.print(apu_cs);
      Serial.print("_not found\r\n");
      delay(100);
      continue;
    }
    //SPC700ユーザープログラム開始
    write_apu(3, START_ADR >> 8, apu_cs);
    write_apu(2, START_ADR & 0xFF, apu_cs);
    write_apu(1, 0x00, apu_cs);//SPC700ユーザープログラム開始モード
    write_apu(0, PROGSIZE + 1, apu_cs);//データ転送終了&ユーザープログラム開始
    Serial.print("APU");
    Serial.print(apu_cs);
    Serial.print("_Start user program\r\n");
    delay(100);//SPC700ユーザープログラム開始までの待ち時間
    //メイン音量レジスタ(MAIN_VOL_L($0C), MAIN_VOL_R($1C))を0にする
    main_vel_pan_set(0,64, apu_cs);//音量, パン
  }
 
  //SPC700でユーザープログラムを実行しているため、
  //SRAMアドレス中の
  //$0000,$0001,$0004    (変数用領域)
  //$00F4 ~ $00F7        (APUポート領域)
  //$0100 ~ $01FF        (スタック領域)
  //$0200 ~ $0300        (ユーザープログラム領域)
  //これらのアドレスへのデータの書き込みは禁止です。
  //※SPC700のユーザープログラム側で、これらのアドレスへの書き込みのプロテクトはしていません。書き込まないように注意してください。

  //MIDI受信用変数初期化
  for(int i=0;i<MIDI_MAX_TR;i++){
   
    midi_vel[i] = 100;//ベロシティ
  }
  for(int i=0;i<16;i++){
    midi_main_vel[i] = 100;//メインベロシティ
    midi_pan[i] = 64;//パン
   
  }
 
}
void loop()
{
  for(int apu_cs = 0; apu_cs < APU_NUM;apu_cs++){
  timeout = 0;
  //①BRR圧縮されたデータをSRAM上に配置
  //BRRをAPUのSRAMへ配置します。
  unsigned int sample0_adr =0x1500;
  unsigned int sample1_adr =0x1500 + brrsample0_len + 1;
  unsigned int sample2_adr =0x1500 + brrsample0_len + brrsample1_len + 2;
  unsigned int sample3_adr =0x1500 + brrsample0_len + brrsample1_len + brrsample2_len + 3;
  write_sram_st_prg(sample0_adr, brrsample0, brrsample0_len, apu_cs);//配置開始アドレス,配列(ATmega328pのプログラムメモリ上)の先頭アドレス,配列サイズ
  write_sram_st_prg(sample1_adr, brrsample1, brrsample1_len, apu_cs);//配置開始アドレス,配列(ATmega328pのプログラムメモリ上)の先頭アドレス,配列サイズ
  write_sram_st_prg(sample2_adr, brrsample2, brrsample2_len, apu_cs);//配置開始アドレス,配列(ATmega328pのプログラムメモリ上)の先頭アドレス,配列サイズ
  write_sram_st_prg(sample3_adr, brrsample3, brrsample3_len, apu_cs);//配置開始アドレス,配列(ATmega328pのプログラムメモリ上)の先頭アドレス,配列サイズ
 
  //②BRRサンプルデータソースディレクトリアドレスに、BRR開始ブロックアドレスとループアドレスを登録(サンプル1開始ブロックアドレス(2Byte),サンプル1ループアドレス(2Byte),サンプル2開始ブロックアドレス(2Byte),サンプル2ループアドレス(2Byte),…,サンプルPCM_N開始ブロックアドレス(2Byte),サンプルPCM_Nループアドレス(2Byte)の順に配置)
  //BRRサンプルアドレス登録関数で開始アドレスとループアドレスを指定。
  etr_brr_vct(0, sample0_adr, sample0_adr + (unsigned int)0x14E2, apu_cs);//サンプル番号, 開始アドレス(2バイト), ループアドレス(2バイト)(ステレオサンプル数をNとするときで、ループ先サンプル点LNとするとき、ループアドレスは((LN+1)/2)*(9/8)となります。)
  etr_brr_vct(1, sample1_adr, sample1_adr + (unsigned int)0x1545, apu_cs);//サンプル番号, 開始アドレス(2バイト), ループアドレス(2バイト)(ステレオサンプル数をNとするときで、ループ先サンプル点LNとするとき、ループアドレスは((LN+1)/2)*(9/8)となります。)
  etr_brr_vct(2, sample2_adr, sample2_adr + (unsigned int)0x15C3, apu_cs);//サンプル番号, 開始アドレス(2バイト), ループアドレス(2バイト)(ステレオサンプル数をNとするときで、ループ先サンプル点LNとするとき、ループアドレスは((LN+1)/2)*(9/8)となります。)
  etr_brr_vct(3, sample3_adr, sample3_adr + (unsigned int)0x1584, apu_cs);//サンプル番号, 開始アドレス(2バイト), ループアドレス(2バイト)(ステレオサンプル数をNとするときで、ループ先サンプル点LNとするとき、ループアドレスは((LN+1)/2)*(9/8)となります。)

  //③BRRサンプルデータソースディレクトリレジスタ(SR_DIR($5D))にBRRサンプルデータソースディレクトリアドレスの上位8bitをセット(下位8bitは$00に固定されています。)
  write_dsp(SR_DIR, BRR_VCT >> 8, apu_cs);//SR_DIRに指定するソースディレクトリアドレスは上位8bitなので8ビット右へシフトする。
 
  //④BRRソース番号レジスタ(SRCN_X($X4))にサンプル番号PCM_Nを指定
  write_dsp(SRCN_X | CH0, 1, apu_cs);
  write_dsp(SRCN_X | CH1, 1, apu_cs);
  write_dsp(SRCN_X | CH2, 1, apu_cs);
  write_dsp(SRCN_X | CH3, 1, apu_cs);
  write_dsp(SRCN_X | CH4, 1, apu_cs);
  write_dsp(SRCN_X | CH5, 1, apu_cs);
  write_dsp(SRCN_X | CH6, 1, apu_cs);
  write_dsp(SRCN_X | CH7, 1, apu_cs);
 
  //⑤エンベロープ関係のレジスタ(ENV1_X($X5), ENV2_X(&X6), GAIN_X($X7))等の設定
  for(int j=0;j<MIDI_MAX_TR;j++){
    etr_adsr_env(j << 4, 15, 1, 4, 8, apu_cs);//チャンネル(4ビット左シフトしたもの限定),Attack ,Decay ,Sustain ,Release
 
  }

  //⑥メイン音量レジスタ(MAIN_VOL_L($0C), MAIN_VOL_R($1C))の設定
  main_vel_pan_set(127,64, apu_cs);//音量, パン

  //未使用のレジスタの初期化
  write_dsp(ECHO_VOL_L, 0, apu_cs);//エコー左音量0
  write_dsp(ECHO_VOL_R, 0, apu_cs);//エコー右音量0
  write_dsp(ECHO_EN, 0, apu_cs);//エコー無効
  write_dsp(PTC_MD, 0, apu_cs);//ピッチモジュレーション無効
  write_dsp(NOISE_EN, 0, apu_cs);//ノイズ無効

  //一応キーオフもする
  write_dsp(KEY_OFF, 0xFF, apu_cs);//全チャンネルキーオフ
  write_dsp(KEY_OFF, 0x00, apu_cs);//全チャンネルのキーオフフラグをもどす


  ///////エコー関係
  if(echo_en == 1){
    //エコー有効
    write_dsp(ECHO_BUF_ADR, ECHO_ADR >> 8, apu_cs);//エコーバッファ先頭アドレス
    write_dsp(ECHO_DLY, echo_delay, apu_cs);//エコー遅延量(4bitで指定、指定値*16msの遅延が得られます。ただし、遅延量が大きいとエコーバッファも大きくしなければいけません。)
   
    write_dsp(ECHO_FB, echo_fg, apu_cs);//エコーフィードバックゲイン
    write_dsp(ECHO_VOL_L, echo_vel_l, apu_cs);//エコー左音量
    write_dsp(ECHO_VOL_R, echo_vel_r, apu_cs);//エコー右音量
   

    //エコーFIRフィルタ係数セット
    for(char efir = 0;efir < 8;efir++)
    {
      write_dsp(ECHO_FIR_X | (efir << 4), echo_fir[efir], apu_cs);

    }
    write_dsp(ECHO_EN, 0xFF, apu_cs);//エコー全チャンネル有効
  }



  //⑦DSP設定レジスタ(DSP_CONF($6C))の設定(DSPリセット処理も行う)
  write_dsp(DSP_CONF, DSP_RESET_EN | DSP_ECHO_DEN, apu_cs);//DSPリセット状態、エコー無効
  write_dsp(DSP_CONF, ((echo_en == 1) ? 0 : DSP_ECHO_DEN), apu_cs);//DSPリセット解除
 

  //音量初期設定
  //①チャンネル音量レジスタ(VOL_X_L($X0), VOL_X_R($X1))のセット
  vel_pan_set(CH0, 64, 64, apu_cs);//チャンネル0, 音量, パン
  vel_pan_set(CH1, 64, 64, apu_cs);//チャンネル1, 音量, パン
  vel_pan_set(CH2, 64, 64, apu_cs);//チャンネル2, 音量, パン
  vel_pan_set(CH3, 64, 64, apu_cs);//チャンネル3, 音量, パン
  vel_pan_set(CH4, 64, 64, apu_cs);//チャンネル4, 音量, パン
  vel_pan_set(CH5, 64, 64, apu_cs);//チャンネル5, 音量, パン
  vel_pan_set(CH6, 64, 64, apu_cs);//チャンネル6, 音量, パン
  vel_pan_set(CH7, 64, 64, apu_cs);//チャンネル7, 音量, パン
  }
  Serial.print("DSP setup ok\r\n");
  while (1)
  {
    if (Serial.available() > 0)
    {
      midi_read(Serial.read());//イベント処理
    }
  }
}

//APUのポートから読み込み
unsigned char read_apu(unsigned char adr, unsigned char cs)
{
  unsigned char readdata;

  DDRB = 0x0F;//arduino_data_pin_input
  DDRD &= ~0xFC;//arduino_data_pin_input

  PORTB = (~0x03 & PORTB) | (adr & 0x03);   //APU_address_write

  digitalWrite(APU_CS_PIN + cs, HIGH);//APU_CS
  delayMicroseconds(1);
  digitalWrite(10, LOW);//pin10_APU_RD
  delayMicroseconds(1);
  readdata = ((PINB & 0x30) >> 4) | (PIND & 0xFC);
  digitalWrite(10, HIGH);//pin10_APU_RD
  digitalWrite(APU_CS_PIN + cs, LOW);//APU_CS
  return readdata;
}

//APUのポートへ書き込み
void write_apu(unsigned char adr, unsigned char writedata, unsigned char cs)
{
  DDRB = 0x3F;//arduino_data_pin_output
  DDRD |= 0xFC;//arduino_data_pin_output

  PORTB = (~0x03 & PORTB) | (adr & 0x03);   //APU_address_write

  PORTD = (~0xFC & PORTD) | (writedata & 0xFC);   //APU_data_write
  PORTB = (~0x30 & PORTB) | (((writedata & 0x03) << 4) & 0x30);   //APU_data_write

  digitalWrite(APU_CS_PIN + cs, HIGH);//APU_CS
  delayMicroseconds(1);
  digitalWrite(11, LOW);//pin11_APU_WE
  delayMicroseconds(1);
  digitalWrite(11, HIGH);//pin11_APU_WE
  digitalWrite(APU_CS_PIN + cs, LOW);//APU_CS

}

//APUへデータ転送
void prog_write(unsigned int adr, unsigned char *writedata, int len, unsigned char cs)
{
 

  while(!((read_apu(0, cs) == 0xAA) && (read_apu(1, cs) == 0xBB))){
    //APUが初期化を完了するまで待機
    static int i_t_c=0;
    delayMicroseconds(1);
    if(i_t_c>=1000){
      Serial.print("Time out : APU");
      Serial.print(cs);
      Serial.print(" not found : PORT0=0xAA and PORT1=0xBB did not return\r\n");
      delay(1000);
      if(AUTO_RESET == 1){
        asm volatile ("  jmp 0");//タイムアウトしたら(APUへの読み書き失敗)Arduinoをリセットする。
      }
      timeout = cs + 1;
      return;
    }
    i_t_c++;
  }
  write_apu(3, adr >> 8, cs);
  write_apu(2, adr & 0xFF, cs);
  write_apu(1, 1, cs);//APUユーザープログラム転送モード
  write_apu(0, 0xCC, cs);
  while(read_apu(0, cs) != 0xCC){
    //APUが転送準備を完了するまで待機
    static int i_t_c=0;
    delayMicroseconds(1);
    if(i_t_c>=1000){
      Serial.print("Time out : APU");
      Serial.print(cs);
      Serial.print(" not found : PORT0=0xCC did not return\r\n");
      delay(1000);
      if(AUTO_RESET == 1){
        asm volatile ("  jmp 0");//タイムアウトしたら(APUへの読み書き失敗)Arduinoをリセットする。
      }
      timeout = cs + 1;
      return;
    }
    i_t_c++;
  }
  for(unsigned int i = 0;i < len;i++)
  {
    write_apu(1, writedata[i], cs);
    write_apu(0, i & 0xFF, cs);
    while(read_apu(0, cs) != (i & 0xFF)){
      //APUが転送データ1バイトを書き込むまで待機
      static int i_t_c=0;
      delayMicroseconds(1);
      if(i_t_c>=1000){
        Serial.print("Time out : APU");
        Serial.print(cs);
        Serial.print(" not found : Failed to transfer the user program\r\n");
        delay(1000);
        if(AUTO_RESET == 1){
          asm volatile ("  jmp 0");//タイムアウトしたら(APUへの読み書き失敗)Arduinoをリセットする。
        }
        timeout = cs + 1;
        return;
      }
      i_t_c++;
    }
  }
}

//~~ユーザプログラム用関数/////////////////////////////

//APU(ユーザープログラム動作中)のSRAMへ書き込み
void write_sram(unsigned int adr, unsigned char sram_writedata, unsigned char cs)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAM_への書き込み手順
  //1.アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.データをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  unsigned char temp_port0;
  int i = 0;
  //Serial.print("apu_port0_read:0x");//デバッグ用
  //Serial.print(read_apu(0), HEX);//デバッグ用
  //Serial.print("\r\n");//デバッグ用
  write_apu(2, adr & 0xFF, cs);
  write_apu(3, (adr>>8) & 0xFF, cs);
  write_apu(1, sram_writedata, cs);
  temp_port0 = read_apu(0, cs);
  write_apu(0, (temp_port0 ^ B00000001) & B11001111, cs);
 
  for(i = 0;i < 100;i++){
    #if DB_MESS
    Serial.print("apu_port0_read:0x");//デバッグ用
    Serial.print(read_apu(0), HEX);//デバッグ用
    Serial.print("\r\n");//デバッグ用
    #endif
    if(((read_apu(0, cs) ^ temp_port0) & B00000100) != 0){
      return;
    }
  }
  timeout = cs + 1;
  #if DB_MESS
  Serial.print("Timeout , APU:");
  Serial.print(cs);
  Serial.print(" , write_sram_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
  #endif
}
//APU(ユーザープログラム動作中)のDSPへ書き込み
void write_dsp(unsigned char adr, unsigned char dsp_writedata, unsigned char cs)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //DSP_への書き込み手順
  //1.アドレス0~7bitをAPUのポート2に書き込む
  //2.データをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=H(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  unsigned char temp_port0;
  int i = 0;
  write_apu(2, adr, cs);
  write_apu(1, dsp_writedata, cs);
  temp_port0 = read_apu(0, cs);
  write_apu(0, ((temp_port0 ^ B00000001) | B00100000) & B11101111, cs);
 
  for(i = 0;i < 100;i++){
    if(((read_apu(0, cs) ^ temp_port0) & B00000100) != 0){
      return;
    }
  }
  timeout = cs + 1;
  #if DB_MESS
  Serial.print("Timeout , APU:");
  Serial.print(cs);
  Serial.print(" , write_dsp_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
  #endif
}

//APU(ユーザープログラム動作中)のSRAMから読み込み
unsigned char read_sram(unsigned int adr, unsigned char cs)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAMから読み込み手順
  //1.アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit1(読み込み要求)を反転させて、APUのポート0に書き込む
  //3.APUのポート0を読み取り、bit3(読み込み完了通知)が反転するまで待つ
  //4.APUのポート1(データ)のデータを読み取る
  unsigned char temp_port0;
  int i = 0;
  write_apu(2, adr & 0xFF, cs);
  write_apu(3, (adr>>8) & 0xFF, cs);
  temp_port0 = read_apu(0, cs);
  write_apu(0, (temp_port0 ^ B00000010) & B11001111, cs);

  for(i = 0;i < 100;i++){
    if(((read_apu(0, cs) ^ temp_port0) & B00001000) != 0){
      return read_apu(1, cs);
    }
  }
  timeout = cs + 1;
  #if DB_MESS
  Serial.print("Timeout , APU:");
  Serial.print(cs);
  Serial.print(" , read_sram_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
  #endif
  return 0;
}

//APU(ユーザープログラム動作中)のDSPから読み込み
unsigned char read_dsp(unsigned int adr, unsigned char cs)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //DSPから読み込み手順
  //1.アドレス0~7bitをAPUのポート2に書き込む
  //2.APUのポート0を読み取り、bit5=H(L:SRAM/H:DSP)に、bit4=L(オートアドレスインクリメント無効)に、bit1(読み込み要求)を反転させて、APUのポート0に書き込む
  //3.APUのポート0を読み取り、bit3(読み込み完了通知)が反転するまで待つ
  //4.APUのポート1(データ)のデータを読み取る
  unsigned char temp_port0;
  int i = 0;
  write_apu(2, adr, cs);
  temp_port0 = read_apu(0, cs);
 
  write_apu(0, ((temp_port0 ^ B00000010) | B00100000) & B11101111, cs);
  for(i = 0;i < 100;i++){
    if(((read_apu(0, cs) ^ temp_port0) & B00001000) != 0){
      return read_apu(1, cs);
    }
  }
  timeout = cs + 1;
  #if DB_MESS
  Serial.print("Timeout , APU:");
  Serial.print(cs);
  Serial.print(" , read_dsp_A:0x");
  Serial.print(adr, HEX);
  Serial.print("\r\n");
  #endif
  return 0;
}

//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)
void write_sram_st(unsigned int adr, unsigned char *sram_writedata, unsigned int len, unsigned char cs)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAM_への連続書き込み手順
  //1.開始アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.最初のデータをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ

  //5.次のデータをAPUのポート1に書き込む
  //6.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント有効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //7.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  //8.「5.次のデータをAPUのポート1に書き込む」へ行く。終了する場合はそのまま終了

  unsigned char temp_port0;
  int i = 0;
  write_sram(adr,sram_writedata[0], cs);//0番目のデータを書いてもまだオートインクリメントはしない。
  for(unsigned int j = 0;j < len;j++){//もういちど0番目のデータを書いた後、アドレスをオートインクリメントする。
    write_apu(1, sram_writedata[j], cs);
    temp_port0 = read_apu(0, cs);
    write_apu(0, ((temp_port0 ^ B00000001) | B00010000) & B11011111, cs);
    for(i = 0;i < 200;i++){
      if(((read_apu(0, cs) ^ temp_port0) & B00000100) != 0){
        break;
      }
    }
    if(i >= 100){
      timeout = cs + 1;//タイムアウト検出用
      return;
    }
  }
 
}

//APU(ユーザープログラム動作中)のSRAMへ書き込み(連続)(プログラム用フラッシュメモリのデータをAPUへ連続転送する場合)
void write_sram_st_prg(unsigned int adr, unsigned char *sram_writedata, unsigned int len, unsigned char cs)
{
  //ユーザープログラム動作中のみこの関数が実行できます。
  //SRAM_への連続書き込み手順
  //1.開始アドレス0~7bitと7~15bitをAPUのポート2と3に書き込む
  //2.最初のデータをAPUのポート1に書き込む
  //3.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント無効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //4.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ

  //5.次のデータをAPUのポート1に書き込む
  //6.APUのポート0を読み取り、bit5=L(L:SRAM/H:DSP)に、bit4=H(オートアドレスインクリメント有効)に、bit0(書き込み要求)を反転させて、APUのポート0に書き込む
  //7.APUのポート0を読み取り、bit2(書き込み完了通知)が反転するまで待つ
  //8.「5.次のデータをAPUのポート1に書き込む」へ行く。終了する場合はそのまま終了

  unsigned char temp_port0;
  int i = 0;
  write_sram(adr,pgm_read_byte_near(sram_writedata), cs);//0番目のデータを書いてもまだオートインクリメントはしない。
  for(unsigned int j = 0;j < len;j++){//もういちど0番目のデータを書いた後、アドレスをオートインクリメントする。
    write_apu(1, pgm_read_byte_near(sram_writedata + j), cs);
    temp_port0 = read_apu(0, cs);
    write_apu(0, ((temp_port0 ^ B00000001) | B00010000) & B11011111, cs);
    for(i = 0;i < 200;i++){
      if(((read_apu(0, cs) ^ temp_port0) & B00000100) != 0){
        break;
      }
    }
    if(i >= 100){
      timeout = cs + 1;//タイムアウト検出用
      return;
    }
  }
 
}

//BRRベクタにサンプルベクタ登録
void etr_brr_vct(unsigned char smp_num, unsigned int start_vct_adr, unsigned int loop_vct_adr, unsigned char cs)
{
  //BRRベクタ登録関数
  //BRRサンプルデータ開始アドレスとループアドレス、サンプル番号を指定すると、BRRベクタに登録します。
 
  //sample0_start_Lbyte, sample0_start_Hbyte, sample0_loop_Lbyte, sample0_loop_Hbyte
  write_sram(BRR_VCT + (smp_num * 4), start_vct_adr & 0xFF, cs);
  write_sram(BRR_VCT + (smp_num * 4) + 1, (start_vct_adr >> 8) & 0xFF, cs);
  write_sram(BRR_VCT + (smp_num * 4) + 2, loop_vct_adr & 0xFF, cs);
  write_sram(BRR_VCT + (smp_num * 4) + 3, (loop_vct_adr >> 8) & 0xFF, cs);
}

//ADSRエンベロープ登録
void etr_adsr_env(unsigned char ch, unsigned char attack, unsigned char decay, unsigned char sustain, unsigned char release, unsigned char cs)
{
  //エンベロープ登録関数(この関数を実行すると自動的にADSRエンベロープが有効になります。)
  //エンベロープ関係のレジスタ(ENV1_X($X5), ENV2_X(&X6), GAIN_X($X7))等の設定
 
  write_dsp(ENV1_X | ch, DSP_ENV_ADSR_EN | ((0x07 & decay) << 4) | (0x0F & attack), cs);
  write_dsp(ENV2_X | ch, ((0x07 & sustain) << 5) | (0x1F & release), cs);
 
}


//音程のセット
void ptc_set(unsigned char ch, unsigned int notenum, unsigned char cs)
{
  char sample_num = 1;
  //音程によってBRRサンプルを変更する
  if(notenum <= 33){
    sample_num = 0;
  }else if(notenum <= 47){
    //sample_num = 1;
    sample_num = 1;
  }else if(notenum <= 60){
    //sample_num = 2;
    sample_num = 2;
  }else if(notenum <= 67){
    //sample_num = 3;
    sample_num = 2;
  }else if(notenum <= 71){
    //sample_num = 4;
    sample_num = 2;
  }else if(notenum <= 77){
    //sample_num = 5;
    sample_num = 3;
  }else if(notenum <= 83){
    //sample_num = 6;
    sample_num = 3;
  }else if(notenum <= 88){
    //sample_num = 7;
    sample_num = 3;
  }else if(notenum <= 94){
    //sample_num = 8;
    sample_num = 3;
  }else{
    //sample_num = 9;
    sample_num = 3;
  }
  write_dsp(SRCN_X | ch, sample_num, cs);
  //音程レジスタ(PTC_X_L($X2), PTC_X_H($X3))のセット
  //MIDIのノート番号からDSPのピッチを求めるテーブルより、ピッチを求めてチャンネルchにセット
  write_dsp(PTC_X_L | ch, (noteFreq[brr_tone[sample_num] + notenum + ((brr_oct[sample_num]) * 12)]) & 0xFF, cs);
  write_dsp(PTC_X_H | ch,((noteFreq[brr_tone[sample_num] + notenum + ((brr_oct[sample_num]) * 12)]) >> 8) & 0x3F, cs);
}

//メインの音量とパンのセット
void main_vel_pan_set(unsigned char vel,unsigned char pan, unsigned char cs)
{
  //音量, パン
  //メイン音量レジスタ(MAIN_VOL_L($0C), MAIN_VOL_R($1C))の設定
  //音量(0~127)とパン(0~127)を乗算して、128で割る(最大出力は127*127/128=126)
  //パンは、64で中央、1で左端、127で右端  (0は1として処理)
  if(pan == 0){
    pan = 1;
  }
  write_dsp(MAIN_VOL_L, (unsigned char)(((unsigned int)vel * (128 - pan)) >> 7), cs);
  write_dsp(MAIN_VOL_R, (unsigned char)(((unsigned int)vel * pan) >> 7), cs);
}

//チャンネルの音量とパンのセット
void vel_pan_set(unsigned char ch, unsigned char vel,unsigned char pan, unsigned char cs)
{
  //チャンネル, 音量, パン
  //チャンネル音量レジスタ(VOL_X_L($X0), VOL_X_R($X1))のセット
  //音量(0~127)とパン(0~127)を乗算して、128で割る(最大出力は127*127/128=126)
  //パンは、64で中央、1で左端、127で右端  (0は1として処理)
  if(pan == 0){
    pan = 1;
  }
  write_dsp(VOL_X_L | ch, (unsigned char)((unsigned int)((unsigned int)vel * (128 - pan)) >> 7), cs);
  write_dsp(VOL_X_R | ch, (unsigned char)((unsigned int)((unsigned int)vel * pan) >> 7), cs);
}

//チャンネルノートオン
void note_on(unsigned char ch, unsigned char cs)
{
  //チャンネル(0~8を左4ビットシフトしたもの)
  //キーオンレジスタ(KEY_ON($4C))のセット
  write_dsp(KEY_OFF, 0, cs);//ふたたびノートオフできるようにする
  write_dsp(KEY_ON, 1 << (ch >> 4), cs);
}

//チャンネルノートオフ
void note_off(unsigned char ch, unsigned char cs)
{
  //チャンネル(0~8を左4ビットシフトしたもの)
  //キーオフレジスタ(KEY_OFF($5C))のセット
  write_dsp(KEY_OFF, 1 << (ch >> 4), cs);
}


//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];

  const char temp = 0; //0の代わり
  //ドラムパートは無視
  if (in_midi_data_buf_l0 == 0x09)
  {
    return;
  }
  //80
  else if (in_midi_data_buf_h0 == 0x08)
  {
    //ノートオフ
    for (int i = 0; i < MIDI_MAX_TR; i++)
    {
      if (midinote[i] == 1)
      {
        if ((midinote_ch[i] == in_midi_data_buf_l0) && (midinote_tn[i] == in_midi_data_buf_1))
        {
          //発音中と同じノートナンバーを見つけたら発音停止
          if (sas_en == 0)
          {
            midinote[i] = 0; //フラグ
            note_off((i & 0x07) << 4,i >> 3);
          }
          else //サスティンon
          {
            midinote[i] = 2; //フラグ
          }
        }
      }
    }
  }
  //90
  else if (in_midi_data_buf_h0 == 0x09)

  {
    //ノートオフ
    if (in_midi_mess[2] == 0)
    {

      for (int i = 0; i < MIDI_MAX_TR; i++)
      {

        if ((midinote_tn[i] == in_midi_data_buf_1) && (midinote_ch[i] == in_midi_data_buf_l0))
        {
          //音量0で発音中と同じノートナンバーを見つけたら発音停止
          if (sas_en == 0)
          {
            midinote[i] = 0; //フラグ
            note_off((i & 0x07) << 4,i >> 3);
           
          }
          else //サスティンon
          {
            midinote[i] = 2; //フラグ
          }
        }
      }
    }
    else
    {
      //ノートオン
      for (int i = 0; i < MIDI_MAX_TR; i++)
      {
        /*if (i == 9)
        {
          //ドラムは無視
          continue;
        }*/
        if ((midinote_tn[i] == in_midi_data_buf_1 ) && (midinote_ch[i] == in_midi_data_buf_l0))
        {
          //同じ音が来たら
          midinote[i] = 1;                         //フラグ
          //一旦ノートオフ
          note_off((i & 0x07) << 4, i >> 3);
          //ノートオン
          ptc_set((i & 0x07) << 4, in_midi_mess[1], i >> 3);//チャンネル, 音程
          midi_vel[i] = in_midi_mess[2];
          vel_pan_set((i & 0x07) << 4, ((int)((int)midi_vel[i] * (int)midi_main_vel[midinote_ch[i]] )>>7), midi_pan[midinote_ch[i]], i >> 3);//チャンネル, 音量, パン
          note_on((i & 0x07) << 4, i >> 3);//ノートオン
          break;
        }
        else if (midinote[i] == 0)
        {
          //和音空きがあったときノートオン

          midinote[i] = 1;                         //フラグ
          midinote_ch[i] = in_midi_mess[0] - 0x90; //ch
          midinote_tn[i] = in_midi_mess[1];        //tn

          //ノートオン
          ptc_set((i & 0x07) << 4, in_midi_mess[1], i >> 3);//チャンネル, 音程
          midi_vel[i] = in_midi_mess[2];
          vel_pan_set((i & 0x07) << 4, ((int)((int)midi_vel[i] * (int)midi_main_vel[midinote_ch[i]] )>>7), midi_pan[midinote_ch[i]], i >> 3);//チャンネル, 音量, パン
          note_on((i & 0x07) << 4, i >> 3);//ノートオン
          break;
        }
      }
    }
  }
  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];
      //vel_pan_set(midinote_ch[in_midi_data_buf_l0] << 4, ((int)((int)midi_vel[midinote_ch[in_midi_data_buf_l0]] * (int)midi_main_vel[midinote_ch[in_midi_data_buf_l0]] )>>7), midi_pan[midinote_ch[in_midi_data_buf_l0]]);//チャンネル, 音量, パン
      break;
    case 0x0A: //パン
      midi_pan[in_midi_data_buf_l0] = in_midi_mess[2];
      //vel_pan_set(midinote_ch[in_midi_data_buf_l0] << 4, ((int)((int)midi_vel[midinote_ch[in_midi_data_buf_l0]] * (int)midi_main_vel[midinote_ch[in_midi_data_buf_l0]] )>>7), midi_pan[midinote_ch[in_midi_data_buf_l0]]);//チャンネル, 音量, パン
      break;
    case 0x78:
    case 0x79:
      for (int i = 0; i < MIDI_MAX_TR; i++)
      {
        //発音停止
        if (midinote[i] == 1)
        {
          midinote[i] = 0;
          note_off((i & 0x07) << 4, i >> 3);
        }
      }
      break;
    case 0x40: //サスティン
      if (in_midi_mess[2] == 0x7F)
      {
        sas_en = 1;
      }
      else
      {
        sas_en = 0;
        for (int i = 0; i < MIDI_MAX_TR; i++)
        {
          if (i == 9)
          {
            //ドラムは無視
            continue;
          }
          if ((midinote[i] == 2)||(midinote[i] == 0))
          {
             
            note_off((i & 0x07) << 4, i >> 3);
            midinote[i] = 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)
    {
      //アクティブセンシング
      //midiout(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++)
            {
              midinote[i] = 0;
              note_off((i & 0x07) << 4, i >> 3);
            }
          }
          if (in_midi_mess[4] == 0x02)
          {
            //GMシステム・オフ
            for (int i = 0; i < MIDI_MAX_TR; i++)
            {
              midinote[i] = 0;
              note_off((i & 0x07) << 4, i >> 3);
            }
          }
        }
      }
    }
  }
}





ATmega328のプログラムメモリが32KBなので、BRRサンプルデータはそう大きく出来ません。APU内のSRAMは64KBあるのでかなり余らしてしまいます。(今回のMIDI受信プログラムでは、余らせた分のRAMはエコーバッファとして利用しています。)

外部にEEPROMやSDカードを接続してそこからBRRサンプルデータを読み取るとよさそうです。


・まとめ


スーパーファミコンのAPUは、サンプルデータを用意する手間はありますが、手軽でちょっといい感じの音源モジュールです。

エコーやハードウェアエンベロープ、ピッチモジュレーション等が備わっているので、
本格的な音作りができます。

スーパファミコンは中古で多く出回っており、入手性はとても良いです。
また、前期型のスーファミに入っているAPUは、モジュールとして取り外すことができるため、部品取りの難易度は低く、おすすめです。





参考サイト

SPC700の命令、DSPのレジスタ、APUの使い方全般
SFC Development Wiki - SPC700 Reference


SPC700の命令、DSPのレジスタ、APUの使い方全般
SNES Spec


SNES回路図
SFC Development Wiki - Schematics, Ports, and Pinouts


APU-Arudino周りの回路
SHVC-SOUNDをPCへ接続する - えぬえす工房


APU-Arudino周りの回路
SNES APU - SNES Hardware page


SPC700コンパイラWLA-DXの使い方等
ゲームギア開発:アセンブラで開発できる「WLA-DX」を動かしてみる


BRRサンプルデータ(ピアノ)
Details for Acoustic Grand Piano - SMW CENTRAL


MIDI受信回路
MIDI規格推奨応用事例(RP)/MIDI規格追記事項(CA)

エコーの関連
FIR Filter - SNESLAB







0 件のコメント:

コメントを投稿

おわり

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.