YMF825boardはFM音源を鳴らす必要な回路をコンパクトに纏め、後はマイコンとSPIインターフェイスで接続すれば簡単に鳴らす事が出来るのですが、サンプルプログラム以降に進む場合公開されているドキュメントだけではどこから進んでいいのか分かり難い所が有るので、sampleプログラム参考にArduino UNOで音色を設定して鳴らすまでを解説してみました。
初期化
####YMF825の初期化
YMF825の初期化は電源の投入時の処理等(元々家電組み込み用のため電源投入時には音が鳴らない)結構面倒くさいのでサンプルプログラムの初期化は呪文と思ってそのまま利用しましょう(^^;)
####SPI通信の設定
SPI通信のモードをMSBFIRST,MODE0に設定します
void setup() {
// put your setup code here, to run once:
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
set_ss_pin(HIGH);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV8);
SPI.setDataMode(SPI_MODE0);
SPI.begin();
init_825();
set_tone();
set_ch();
}
SPIの初期化時の速度は8MHzにしても十分ですので
SPI.setClockDivider(SPI_CLOCK_DIV2);
に変更しておいた方が後々の事を考えると良いでしょう。
レジスタへの書き込み (Single Write)
音色データ以外のパラメータの設定はSingleWriteモードで行います。
書き込み方法
- YMF825のSSピンをlowにする
- addressとdataの2byteのデータを送信
- SSピンをhighにして書き込み完了。
書き込む発音チャンネルの指定
12~20番地の各音色チャンネルのパラメータには11番地(Synthesizer Setting)の値で発音チャンネルを指定してからSingleWriteで書き込みます。
音色設定 (Burst Write)
YMF825は1音30byteの音色データを16音、480byteのテーブルを内部に持ち、音色データをheaderとend Dataを加えて連続してCONTENTS_DATA_REG(#7)へ書き込む事で音色を設定します(発音チャンネルと音色は独立しており、発音チャンネルにどの音色番号を使用するか指定する)
設定は音色単位で行う必要がありさらにテーブルに書き込む音色数は任意に設定出来ますが、順番は変更出来ないため16番目の音色のパラメータを1つ変更するためには480byteすべてのデータを書き込む必要があり、演奏中にパラメータを変更する場合などこの書き込み速度が大変重要になります。
void set_tone(void){
unsigned char tone_data[35] ={
0x81,//header ・・・・0x80+設定する音色の数
//T_ADR 0
0x01,0x85,
0x00,0x7F,0xF4,0xBB,0x00,0x10,0x40,
0x00,0xAF,0xA0,0x0E,0x03,0x10,0x40,
0x00,0x2F,0xF3,0x9B,0x00,0x20,0x41,
0x00,0xAF,0xA0,0x0E,0x01,0x10,0x40,
0x80,0x03,0x81,0x80, //data end 4byte 固定値
};
if_s_write( 0x08, 0xF6 ); //Burst Writeモードに設定
delay(1);
if_s_write( 0x08, 0x00 );
/* 7番地へ連続書き込み */
if_write( 0x07, &tone_data[0], 35 );//write to FIFO
}
YMF825にはburst Writeと言う連続書き込みのモードがあり、音色の転送モードにした後データを連続して書き込む事が出来るので工夫すればほぼリアルタイムに音色の変更をする事も可能です。
####手順
- Single Writeで8番地に0xF6を書き込んだ後にもう一度8番地に0x00を書き込みBurst Writeモードにする。
- SSピンをlowに
- アドレス7番地を送信(0x07)
- 音色データを連続して書き込み header+(30byte*音色数)+footer
- SSピンをhigh
注)BurstWriteモードではアドレスはSSピンをlowにした後最初に1回送るだけ。
SSピンがhighになった時点で音色の変更が反映されます。
####Sequencer Setting #8
adr | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
#8 | AllKeyOff | AllMute | AllEGRst | R_FIFOR | REP_SQ | R_SEQ | R_FIFO | START |
8番地のSequencer Settingの値ですがデータの転送時に | ||||||||
AllKeyOff,AllMute,AllEGRstを有効にしているので | ||||||||
0xf6を0x16にする事で発音中の音色を止めることなく音色が変更出来ます。 |
音量
Master Volume #25
25番地のbit8~bit2の6bitでマスターボリューム指定
音が割れるようだったら初期化ルーチンの値を小さくする。
発音チャンネルのVolume #12,#16
adr | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
#12 | 0 | VoVol4 | VoVol3 | VoVol2 | VoVol1 | VoVol0 | 0 | 0 |
#16 | 0 | ChVol4 | ChVol3 | ChVol2 | ChVol1 | ChVol0 | 0 | 0 |
12番地(VoVol)bit6~bit2 (本家sample2ではvelocity)
16番地(ChVol)bit6~bit2 (本家sample2は0x71に固定)
で発音チャンネルの音量を指定。
(MSBが0なので下位2bitを無視すればMIDIのvelocityやPartLevel(0~127)をそのまま書き込める)
##Pitch Bend ,Fine Tuning
####INT,FRAC #18,#19
元の音色データを変えずに最大上下2オクターブの幅で周波数を変更出来る、音色の補正やピッチベンドに使用すると便利。
adr | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
#18 | 0 | 0 | 0 | INT1 | INT0 | FRAC8 | FRAC7 | FRAC6 |
#19 | 0 | FRAC5 | FRAC4 | FRAC3 | FRAC2 | FRAC1 | FRAC0 | 0 |
(INT1,INT0).(FRAC8~FRAC0)
整数部2桁小数部9桁の固定小数点値、中間は01.000000000(初期値)
00.000000000で2オクターブ低い音
11.111111111で2オクターブ高い音
MIDIのPitchBendに使う時には2次関数的に変化するので8bitマイコンでその都度計算するのは遅い、本家sample2では8bitにしてPitchBendRange固定で実装している。
Note On
#13,#14のFnumとBLOCKで音程を、#15のToneNumで音色を指定、KeyONに1をセット,音量はあらかじめChVolとVoVolで設定しておく。
#####手順
- #11 発音チャンネルを設定
- #12,#16 VoVol,ChVol(音量)
- #13,#14 BLOCK,FNUM(音程)
- #15 KeyOn,ToneNum 音色番号セット、キーオン
####CRGD_VNO #11
adr | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
#11 | 0 | 0 | 0 | 0 | CRGD_VNO3 | CRGD_VNO2 | CRGD_VNO1 | CRGD_VN0 |
CRGD_VNOで#11~#20のレジスタの発音チャンネルを指定します。
####Fnum Block #13#14
adr | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
#13 | 0 | 0 | FNUM9 | FNUM8 | FNUM7 | BLOCK2 | BLOCK1 | BLOCCK0 |
#14 | 0 | FNUM6 | FNUM5 | FNUM4 | FNUM3 | FNUM2 | FNUM1 | FNUM0 |
BLOCKでオクターブ指定、Fnumは本家sample2 fmvoice.cではテーブルからfnum値16bitを引っ張っているが、レジスタに書き込むのにビットシフト等が必要なので計算済みの値を8bitのテーブルfnumhi[],fnumlo[]とかにしてMIDIのNote Noにあらかじめ計算して対応する方がある。
あらかじめ計算したFnumテーブルの例
const uint8_t fnum_hi_tbl[128] PROGMEM = {
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x18,0x18,
0x18,0x18,0x18,0x20,0x20,0x20,0x20,0x28,0x11,0x11,0x19,0x19,0x19,0x19,0x19,0x21,
0x21,0x21,0x21,0x29,0x12,0x12,0x1a,0x1a,0x1a,0x1a,0x1a,0x22,0x22,0x22,0x22,0x2a,
0x13,0x13,0x1b,0x1b,0x1b,0x1b,0x1b,0x23,0x23,0x23,0x23,0x2b,0x14,0x14,0x1c,0x1c,
0x1c,0x1c,0x1c,0x24,0x24,0x24,0x24,0x2c,0x15,0x15,0x1d,0x1d,0x1d,0x1d,0x1d,0x25,
0x25,0x25,0x25,0x2d,0x16,0x16,0x1e,0x1e,0x1e,0x1e,0x1e,0x26,0x26,0x26,0x26,0x2e,
0x17,0x17,0x1f,0x1f,0x1f,0x1f,0x1f,0x27,0x27,0x27,0x27,0x2f,0x10,0x10,0x18,0x18,
0x18,0x18,0x18,0x20,0x20,0x20,0x20,0x28,0x11,0x11,0x19,0x19,0x19,0x19,0x10,0x1e,
};
const uint8_t fnum_lo_tbl[128] PROGMEM = {
0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x7a,0x11,0x29,
0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,0x42,0x5d,0x79,0x17,
0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,
0x65,0x7a,0x11,0x29,0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,
0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,0x42,0x5d,0x79,0x17,
0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,
0x65,0x7a,0x11,0x29,0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,
0x42,0x5d,0x79,0x17,0x37,0x59,0x7d,0x22,0x65,0x7a,0x11,0x29,0x42,0x5d,0x65,0x5d,
};
/* 読み出し例 note_noはMIDIのノートナンバー */
uint8_t fnum_hi,fnum_lo;
fnum_hi = pgm_read_byte(&fnum_hi_tbl[note_no]);
fnum_lo = pgm_read_byte(&fnum_lo_tbl[note_no]);
/* fnum_hiを#13,fnum_loを#14へ書き込む */
####KeyOn #15
adr | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
#15 | 0 | KeyOn | Mute | EG_REST | ToneNum3 | ToneNum2 | ToneNum1 | ToneNum0 |
使用する音色番号をToneNum(4bitで0-15)にセットしKeyOnを1にすると音が鳴る。 |
####ポリフォニックに鳴らす時
複数音鳴らしたい時には発音要求に対して空いている発音チャンネルを割り当てないといけません、簡易な割り当て処理(DVA)を上げておきます。
#define MIDITONE_MAX 16 //最大発音チャンネル数
int active_voice_num = 0; //発音中のチャンネル数
int voice_queue_top = 0; //キューの先頭
int voice_queue_tail = 0; //キューの末尾
uint8_t voice_queue[MIDITONE_MAX]; //チャンネルNoのキュー
uint8_t ch_midino[MIDITONE_MAX]; //発音チャンネルのノートナンバー
//----------------------------------------
void init_voice(){/* 初期化 */
for (int i = 0; i < MIDITONE_MAX; i++) {
voice_queue[i] = i;
ch_midino[i] = 0;
}
}
/*空き音色チャンネルを得る*/
int getFreeChannel(){
int voice_ch;
if (active_voice_num == MIDITONE_MAX) {
return -1;
}else{
voice_ch = voice_queue[voice_queue_top++];
if (voice_queue_top == MIDITONE_MAX) {
voice_queue_top = 0;
}
active_voice_num++;
return voice_ch;
}
}
/* KeyOffする音色チャンネルを得る */
int getOffChannel(int midiNoteNo){
for (int i = 0; i < MIDITONE_MAX; i++) {
if (ch_midino[i] == midiNoteNo) {
ch_midino[i] = 0;
voice_queue[voice_queue_tail++] = i;
if (voice_queue_tail == MIDITONE_MAX) {
voice_queue_tail = 0;
}
active_voice_num--;
return i;
}
return -1; //対象発音チャンネル無し
}
同一音色ならばこれぐらいの処理で問題は無いでしょう、16音以上鳴らそうとした時の処理は一番古い音色を強制的にKeyOffしてその音色チャンネルを鳴らせば良いと思います。
Note Off
#15のKeyOnを0にするとKeyOffとなりReleaseFaseへ移行する、ピアノに例えるなら鍵盤を離した状態、ここで音を止めたかったら音色のReleaseRateを15にするか#15のMuteを1にする
注)KeyOff時にToneNumを鳴らした音色番号以外の値にするとReleaseFaseがら別の音色になる(そう言った技法があります)
##どうやって鳴らす?
今まで解説したのはあくまで音の出し方であって、楽器にするなり曲を演奏するのには音色と演奏データの問題が出てきます、YMF825はPCに使用されたFM音源Chipと違いVGMファイルの様なログ形式のデータが有りません、音色も本家サイトや有志が公開されているものの種類が少なく、音色のフォーマットもMidiのSysEx形式が決まっていますが、この形式で公開されているデータは稀です、音色の編集ソフトにおいてはドライバの作者が自分で作っているのが殆ど・・・・じゃあ?
####音色の問題
YMF825は携帯電話に使われていたMA-3のFM音源部と同等なので、YAMAHAが過去に出していたオーサリングツールを使って音色作成が出来ます、本家のリンクは切れているのでアーカイブ先SMAFオーサリングツールMA-5辺りのツールを使用するか、先人の作ったツールを利用したり音色のパラメータをMMLの時のようにtextベースで変更することになりいずれにしよ基本波形導入後のOPL系のFM音源の知識を求められます。
〇MA-3のGM準拠音色をコンバートしたもの(30バイトのレジスタ書き込みデータ形式)YMF825 GMライクな音色
####曲を鳴らす
携帯のSMAFフォーマットのデータを利用したり、MIDIで曲と音色データを作成し演奏されています、この辺りは作者の得意な分野で色々な方法が公開されておりArduinoでもMIDIデータをPC側で処理するかマイコンで処理するか色々調べると面白いです。
##最後に
私の本家サンプル回路そのままで使えるMIDI音源と音色エディタの作例へのリンクを置いておきます
YMF825MIDI_CONTROL_for_Arduino
YMF825はFM音源の中でも後期のChipになり、豊富な基本波形を使った非常に多様な音作りが出来ます、是非とも自分だけの音色を鳴らして楽しんで下さい。
####参考
本家YMF825boardサイト
以下参照コードへのリンク
sample1
ymf825board_sample1.ino
sample2
arduino/fmsd1_ino.cpp
YMF825の初期化とSPI初期化、送信
common/fmvoice.c
Fnumの計算とPitchBend KeyOn,Off
common/fmtone.c
音色の転送
FnumberとINT.FRACに関するExcelファイル
参考になる解説サイト
Lost Technology (YMF825(SD-1)ざっくりまとめ
2021/7/25 keyOn時の#11と簡易DVAについて加筆