ゲヌマ・フジガヤ2(fujigaya2) blogのまとめ - 吊りかけ/VVVF音部分の解説(MP3サウンドデコーダV5用)
 名古屋電鉄様の46.MP3サウンドデコーダV5に搭載されている疑似走行音(VVVF音)部分の解説です。
 一部のニッチな方から中身を教えて!という話があったので、説明をします。
 ぜひ、自分だけのMp3サウンドデコーダを作ってみてください。
(なお、自分で読み返してもかなり分かりづらいので、誰かわかりやすく書き直してくれると嬉しいです)

 お約束ですが、スケッチの書き換えは自己責任でお願いいたします。

原理/構成

原理

 ATMELのマイコンATMEGA328PのPWMを使用して、サンプリング音を出力して、速度に比例した疑似的な吊りかけ音、VVVF音を出力します。

構成

ソフトウェア
  • VVVF_sound.cpp・・・疑似音生成の本体です。DCCデコーダ部分からスピードをもらって、疑似音を生成します。
  • VVVF_sound.h・・・ヘッダファイルです。
  • kasoku.h・・・吊りかけ音、VVVF音の速度に対してどの周波数で音を鳴らすかのLUT(look up table)です。これを変更すると、いろいろなVVVF音のバリエーションが作れます。
  • test23_loud.h・・・現状の吊りかけ音、VVVF音の元サンプリングファイルを配列にしたものです。この音を変更すると、音色を変えることができます。現在、サンプリングレートは16KHzにしています。このため、若い人(0〜10代)にはサンプリング周波数のモスキート音が聞こえるようです。Mp3サウンドデコーダV5にLPFが入っており1.8kΩ、0.1uFよりカットオフ周波数は884Hzに設定されています(計算はこちら
  • motor_func.h・・・LPF関数(Low Pass Filter:速度の変化を滑らかにする)を使用しています。
  • TimerOne.h・・・PWMを生成するためのArudinoのライブラリです。こちらのインラインバージョンを使用しています。
ハードウェア
VVVF_sound.hに
#define VVVF_SOUND_PIN 9
として、音波形出力ピンを9としています。
Timer1を使用していますので、10とすれば10ピンに出力できると思います。

ソフトの使用方法

ino(スケッチ)からどう呼び出すかを解説します。
  • setup関数内
1.VVVF_Setup()で、出力ピン(9pin)、Timer1の初期化をします。
2.各種CV値をVVVF_soundにコピーします。
 VVVF_SetCV(3, gCV3_AccRatio);
 VVVF_SetCV(4, gCV4_DecRatio);
 VVVF_SetCV(47, gCV47_SoundSwitch);
 VVVF_SetCV(48, gCV48_SoundState);
 VVVF_SetCV(49, gCV49_SoundNotch);
 VVVF_SetCV(50, gCV50_SoundVolume);
3.VVVF_Init()で、kasoku.hのLUTを本体RAMにコピーします。以前、この処理が遅いためCV値の書き換えがうまくいかない不具合がありました。
  • loop内
4.タイマー(33msecごと)でVVVF_Cont(gSpeedCmd_vvvf, gFuncBits[3])を呼びます。
 gSpeedCmd_vvvfは速度0%〜100%が0〜255になる速度値です。
 gFuncBits[3]はVVVF音のOn/Offビットです。

内部定数/変数/関数の説明

定数

VVVF_sound.h
  • #define VVVF_SOUND_PIN 9
→PWM出力のpin番号です。9,10が選べると思います。
VVVF_sound.cpp
  • #define WAV_DATA1 test23_data
→サンプリング波形(test23_loud.h)の配列先頭アドレスです。
  • #define WAV_LENGTH1 test23_length
→サンプリング波形(test23_loud.h)の配列長さです。
  • #define REF_WAVE_FREQ1 (1000000 / (WAV_LENGTH1 * 62.5))
→サンプリング波形(test23_loud.h)の配列番号指定用の除数です。62.5という定数は 62.5[us]=1[s]/16000から来ています。
  • #define WAV_DATA* test23_data
  • #define WAV_LENGTH* test23_length
  • #define REF_WAVE_FREQ* (1000000 / (WAV_LENGTH1 * 62.5))
→2,3も同様です。ですので、サンプリング波形は3つ使えます。
  • #define SHIFT_PWM 5
→内部的にサンプリング波形の小数点以下を何桁設定するかの定数です。5なので5桁設定しています。
 サンプリング波形の配列の番号の指定(16ビット型)に使用します。小さくすると(小数点以下の誤差が大きくなるので)波形が汚くなり、大きくすると桁あふれのため音がバグります。
  • #define COUNT_CONSTANTSPEED 33/* 33d=1sec */
→ノッチオフ時、疑似音を何秒で止めるかの値です。33で一秒です。
  • #define THRESHOLD_RATIO 40
→(未使用)
  • #define CARRER_FREQ 16000
→サンプリング波形(test23_loud.h)のサンプリング周波数です。

変数

VVVF_sound.cpp
  • int current_period1 =(double)1000000 / CARRER_FREQ;//VVVF周期
→Timer1の割り込み周期です。CARRER_FREQ = 16000なので、16000Hzを指定しています。
  • volatile unsigned int pwm_state1 = 0;
  • volatile unsigned int pwm_state2 = 10;
  • volatile unsigned int pwm_state3 = 20;
→サンプリング波形1,2,3の割り込み時の配列番号です。固定小数を含んでいますので、実際の配列番号はpwm_state1>>SHIFT_PWM です。
  • unsigned int pwm_add1 = 0;
  • unsigned int pwm_add2 = 10;
  • unsigned int pwm_add3 = 20;
→サンプリング波形1,2,3の配列で次は何個飛ばしするかです。固定小数を含んでいます。
  • unsigned int old_pwm_add1 = 0;
  • unsigned int old_pwm_add2 = 10;
→リバーブ効果(残響音)用のpwm_addです。
  • unsigned int pwm_shift = 0;
→CV50で設定したVVVF音ボリューム用の内部変数でどれだけ音を下げるかの値です。pwm_shift = 8 - SoundVolumeです。
  • int current_duty_a = 0;
→(未使用)
  • int gAccRatio = 20;
→CV3(加速値)の内部変数です。
  • int gDecRatio = 20;
→CV4(減速値)の内部変数です。
  • int gNotchSpdCount = 0;
→ノッチオフのカウントです。
  • uint8_t SoundSwitch = 0;
→CV47(kasoku.hのどれを選ぶか)の内部変数です。
  • uint8_t SoundState = 1;
→CV48の内部変数です。現在未使用です。
  • uint8_t SoundNotch = 0;
→CV49(ノッチオフ切り替え)の内部変数です。
  • uint8_t SoundVolume = 10;
→VVVF音のボリュームの内部変数です。
  • long gVVVFLPF_buf = 0;
→(未使用)
  • float PrevSpeed = 0;
→ひとつ前のスピードの保持変数です。
  • boolean accel_flag = false;
→ノッチオフの時に使う加速中かどうかのフラグです。

関数

VVVF_sound.cpp
  • extern void VVVF_Setup()
→Timer1の出力ピンの設定、割り込み周期の設定を行います。
  • void VVVF_SetCV(uint8_t iNo, uint8_t inData)
→VVVF関連のCV番号(iNo)、CV値(inData)を内部変数にコピーします。
  • extern void VVVF_Cont(int inPWMFreq, uint8_t inF2Flag)
→スピード(inPWMFreq)とノッチオフのフラグ(inF2Flag)をもらって、音を出すかや、VVVFの現在の周波数などの計算をします。
  • int VVVF_Freq( int inPWMFreq)
→スピード(inPWMFreq)をもらって、Kasoku配列と現在のスピードからサンプリング音の周波数を決定します。
  • void VVVF_Init()
→CV47に基づいてKasoku配列からRAMへ音の周波数配列をコピーします。
  • void vvvf_int1()
→16000Hz(62.5us)ごとに呼び出される音の波形生成割り込みです。
現在の音の生成は以下になっています。
(pgm_read_byte_near(&WAV_DATA1 [pwm_state1 >> SHIFT_PWM]) * 3 + pgm_read_byte_near(&WAV_DATA3 [pwm_state3 >> SHIFT_PWM])) >> pwm_shift)
2つの波形から音を生成しており、主周波数の音(WAV_DATA1):副周波数の音(WAV_DATA3)=3:1の波形で出しています。
割り込みなので、計算量が多すぎると、Mp3サウンドデコーダ全体の動きが遅くなり、動作がおかしくなりますので、注意が必要です。

VVVF配列(kasoku.h)の説明/作り方

Kasoku.hの中身

 配列は二つの部分に分かれています。上配列でVVVFの音色の切替ポイントを示しています。下配列がVVVFの音階とモーターの強さの配列です。
注意)このページのソースをスケッチにコピーすると表示のために全角スペースなどを使っているのでエラーになします。
  • 上の配列 progmem_ref_kasoku[10]
 0〜9までのVVVFの配列の切替ポイントを指示しています。
const PROGMEM int progmem_ref_kasoku[10] =
{
0,//0:吊りかけ音 10- 110
5,//1:吊りかけ音 10 - 220
10,//2:吊りかけ音 10- 330
15,//3:吊りかけ音 20 - 110
20,//4:吊りかけ音 20 - 220
25,//5:京急ドレミファ
40,//6:E231系
45,//7:抵抗制御
50,//8:VVVF 東洋系
55,//9:E231
60//終わり+1
};
  • 下の配列 progmem_kasoku[55][8]
 VVVF配列のそのものです。Duty初め、Duty終わりは以前この配列でモーターも回していた名残で、現在は意味がないです。
速度は0%〜100%のとき0〜1023です。周波数はHzで入れられますが、合っているかどうか確認していません。
また高い周波数で値を変更していくと、連続的に音が変わらないことがあります。
周波数1、周波数2があるのは、和音を出力するためです。周波数2の音は周波数1の1/3の振幅の波形になります。
標準の配列だと以下のように作っております。(東洋系VVVFの例)
速度スタート速度エンド周波数1スタート周波数1エンド周波数2スタート周波数2エンドダミーダミー
領域10170277369554739512512
領域2171330246415587987512512
領域3331500130207370587512512
領域450110212073535871000512512
領域510221023353353500500512512
表の例で、領域2は速度(1023で最高速)の171〜330の間に周波数1は246Hz〜415Hzに変わり、周波数2は587Hz〜987Hzに変わるということになります。

//速度領域始め、終わり、周波数始め、終わり、周波数2始め、2終わり、-、-
const PROGMEM int progmem_kasoku[55][8] =
{   
//省略///

   //8:VVVF 東洋系
   {   0, 170, 277, 369, 554, 739, 512, 512},
   { 171, 330, 246, 415, 587, 987, 512, 512},
   { 331, 500, 130, 207, 370, 587, 512, 512},
   { 501,1021, 207, 353, 587,1000, 512, 512},
   {1022,1023, 353, 353, 500, 500, 512, 512},

//省略//
};

VVVF配列の作り方

 VVVF音は音に厚みを出すため、2和音を使用しています。
作り方ですが、まず音の高さを確認するため、WaveToneというソフトで加速音の音声ファイルを読み込みます。
すると、加速するに従っての周波数が分かるようになります。下記のは東洋系のVVVF音についてMp3ファイルを読み込んだものです。

カーソルを合わせると周波数が表示されますので、メモっておきます。

これをKasoku.hに書き込みます。和音は適当です。音を聞きながら自分的に納得するように調整してみてください。私としてはこんな感じになりました。(もともとの吊りかけ音のサンプリング音はすでに倍音を含んだものになります)

 イメージグラフが書ける表はこちらです。(一部変かも)
 この表は、5段のもののため、ドレミファVVVFみたいのを作るときは、もう少し段数を増やす必要があります。

吊りかけ音サンプリング音源の作り方

 まずは電車の音を録ってきます。
 編集はAudacityで行います。(Audacityの操作方法はネットで探してください。)
(1)音ファイルをAudacityで開きます。
(2)トラック→ステレオからモノラルへ を選んで、モノラルデータにします。
(3)吊りかけ音の特徴はノコギリ波とのことで、低音でそうなってそうな所を探して、切り出します。1周期だけ切り出します。

 私は35msぐらいの時間のデータを選びました。
(4)音が小さいときはエフェクト→増幅を選ぶと波形を大きくできます(大きくできないときは「クリッピングを可能にする」にチェックを入れておきます)。振幅が1を超えない程度に大きくすることをお勧めします。
(5)1周期選んだあとZキーを押すと、勝手に波形の0点に時間を合わせてくれます。Shift+Spaceキーを押すと切り出した部分をリピート再生してくれます。よさそうな波形を探してください。
(6)切り出します。ファイル→新規作成でAudacityをもう一個立ち上げ、選択領域をコピー&ペーストします。

(7)画面の左下のプロジェクトのサンプリング周波数(Hz)から、サンプリング周波数を44100Hzに変更します。これは適当に補間された大きな波形データが欲しいためにやっているだけです。(ほかの物を試していないのでわかりませんが、もしかしたらこの設定によって元の音の周波数が変わるかもです)
(8)ファイル→オーディオの書き出しでファイルを書き出します。
ファイルの種類は「その他の非圧縮ファイル」
フォーマットは、ヘッダ:「WAV(Microsoft)」、エンコーディング:「Unsigned8-bitPCM」
でよいと思います。
ファイル名の拡張子は.WAVにしておきます。
 私のサンプリングファイルではファイルサイズは1.5KBぐらいでした。
(9)DSWav2C.exeを立ち上げ、Openボタンから先ほどのWAVファイルを読み込ませます。「Sample rate of the Wave file is not 8kHz.」と怒られますが「OK」ボタンを押して無視します。(DSWav2CはYaasan様の作です)
 Saveボタンを押して、適当なファイル名.hでセーブします。(test23_loud.hとすると、デコーダのスケッチをいじらないで済むかもです)

 なお、私のサンプリングファイルではデータ数が1500程度になっていました。
 データ数をもっと大きくしたらもっといい音になるかもですがやっていません。

 このファイル名をVVVF_sound.cppで読み込んでいるサンプリング波形ファイル(test23_loud.h)と差し替えて、参照ファイル名も変えてやって、コンパイルして、書き込んで、動けば、オリジナル音源のスケッチの完成です。