ページサイズが大きすぎてインデックスされない問題があるため、記事を分割しました。
の続きです。
ようやく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;
}
SNESのAPUの演奏テスト#よいまちカンターレ#SPC700 pic.twitter.com/OL3QkyI7Xk
— oy (@0x6f_0x79) January 3, 2022
・MIDIを受信する
最後に、MIDIを受信するプログラムを追加して完成です。
・回路図
MIDIを受信するための回路を追加しました。
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受信回路
エコーの関連
FIR Filter - SNESLAB
0 件のコメント:
コメントを投稿