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 件のコメント:

コメントを投稿