Blogの記事をまとめたつもりのサイトです。

 名古屋電鉄様の46.MP3サウンドデコーダV5に搭載されている疑似走行音(ディーゼル音)部分の解説です。
 一部のニッチな方から中身を教えて!という話があったので、説明をします。
 やっていることは、スピード指令に合わせて、IAM_ADPCM音のリピート再生です。なぜ普通のWAVじゃないかというと、ATMEGA328PのROMが少ないから圧縮したというだけです。IMA ADPCMは16itですが、再生は8bitで行っています。
 ぜひ、自分だけのMp3サウンドデコーダを作ってみてください。
 なお、自分で読み返してもかなり分かりづらいので、誰かわかりやすく書き直してくれると嬉しいです。
 また、もしかしたら、下記を直さずに0から作り直したほうが良いかもです。

 お約束ですが、スケッチの書き換えは自己責任でお願いいたします。
 現在、記憶を頼りに書いておりますので、一部間違えているかもです。お気づきの点はご連絡ください。

原理/構成

原理

 ATMELのマイコンATMEGA328PのPWMを使用して、サンプリング音を出力して、加速に合わせたディーゼル音を出します。

構成

ソフトウェア
  • VVVF_sound.cpp・・・疑似音生成の本体です。DCCデコーダ部分からスピードをもらって、疑似音を生成します。
  • VVVF_sound.h・・・ヘッダファイルです。
  • kasoku.h・・・未使用です。
  • "D205_16000_WAV6L.h"・・・加速時(1段階目)のIMA ADPCMの音です。フォーマットは16KHz,16bitです。
  • "D205_16000_WAV6U.h"・・・加速時(2段階目)のIMA ADPCMの音です。
  • "D205_16000_WAV6UU.h"・・・加速時(3段階目)のIMA ADPCMの音です。
  • "D205_16000_WAV3.h"・・・アイドル時のIMA ADPCMの音です。
  • 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.各種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);
 VVVF_SetCV(52, gCV52_SoundLoopTiming);
2.VVVF_Setup()で、出力ピン(9pin)、Timer1の初期化をします。IMA_ADPCMの各値を初期化します。
  • loop内
4.タイマー(33msecごと)でVVVF_Cont(gSpeedCmd_vvvf)を呼びます。
 gSpeedCmd_vvvfは速度0%〜100%が0〜255になる速度値です。

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

※他のスケッチからの流用のため現在、大量の未使用定数、変数があります。

定数

VVVF_sound.h
  • #define VVVF_SOUND_PIN 9
→PWM出力のpin番号です。9,10が選べると思います。
VVVF_sound.cpp
  • #define WAV_DATA1 test23_data
→サンプリング波形(test23_loud.h)の配列先頭アドレスです。
  • #define COUNT_CONSTANTSPEED 30
→ノッチオフ用の音を止めるタイマの定数です。VVVF_Cont内で使用し、33msごとに1ずつ減っていくので、例えば値が30だと、30*33ms=990msでノッチオフに移行します。
  • #define CARRER_FREQ 16000
→IMA_ADPCMのキャリア周波数です。

変数

  • int uplimit; //VVVF配列の上限
  • int kasoku[15][8];//VVVF配列
→未使用です。
  • int current_period1 = (double)1000000 / CARRER_FREQ; //Diesel周期
→IMA_ADPCMのキャリア周波数です。
  • int current_period2 = (double)1000000 / CARRER_FREQ; //Diesel周期
→未使用です。
  • volatile unsigned int pwm_state = 0;
  • unsigned int pwm_add = 0;
  • int pwm_mask = 0xff;
  • int pwm_shift = 8;
  • int current_duty_a = 0;
→未使用です。
  • int gAccRatio = 20;
  • int gDecRatio = 20;
  • int gNotchSpdCount = 0;
→VVVF_Cont内で使用する加速、減速、ノッチオフ用の変数です。
  • uint8_t SoundSwitch = 0;
→CV47の内部変数で10で通常音、11で爆音だったと思います。
  • uint8_t SoundState = 1;
→CV48ですが未使用です。
  • uint8_t SoundNotch = 0;
→CV49ですが未使用です。
  • uint8_t SoundVolume = 10;
→CV50ですが未使用です。
  • uint8_t gSoundLoopTiming = 8 ; // Variable set of Sound loop time by MECY 2017/05/14
→CV52です。加速時のサウンドの移行タイミング値です。1〜40で値を大きくすると早く移行します。
  • int SoundWave_coeff = 1;
→未使用です。
  • float PrevSpeed = 0;
→VVVF_Cont内で使用する変数です。
  • boolean vvvf_first_flag = true;//VVVF_Initを一回呼び出すためのFlag
  • boolean accel_flag_prev = false;
→未使用です。
  • boolean accel_flag = false;
→VVVF_Cont内で使用する変数です。
  • volatile uint8_t gSound_played = 0;
→現在の鳴らしている音ファイル番号です。
  • uint8_t gSound_playnext = 0;
→次に鳴らす音ファイル番号です。
  • int gSound_Length = 0;
→音ファイルの長さですが、現在かなり適当な(適切でない)使い方をしています・・・。
  • int gSound_offset = 0; // aya add RIFF フォーマットをそのまま使っているのでdata chunkまでoffsetを履かせる
  • volatile uint16_t sample; // volatile を使う意味があるかな?
  • uint16_t ndata; // 11/9 block数をカウント
  • byte lastSample; //
  • uint16_t nBlockAlign = 252;
  • IMADEC imad;
→IMA ADPCM関連の変数です。IMA ADPCMについては、DCC館様のページを参考(というかほぼそのまま)に使用しております。
  • uint16_t nspeed;
→VVVF_Cont内で使用する変数です。

関数

VVVF_sound.cpp
  • extern void VVVF_Setup()
→Timer1の出力ピンの設定、割り込み周期の設定、IMA ADPCMの設定を行います。
  • 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)をもらって、どの音を出すかを決めています。決めた音はgSound_playnext値です。
また、nspeedもリピートスピードに絡んでいます。大きくすると音再生前にリピートします。
  • void vvvf_int1()
→16KHz(62.5us)ごとに呼び出される音の波形生成割り込みです。
現在の音の生成は以下になっています。
gSound_playedの音番号の音を再生します。
音のリピートは、gSound_Lengthですが、リピート間隔は音の終了を待たずに「gSound_Length - nspeed」よりも長くなったらリピートする。としています。nspeedは加速すると値が大きくなりますので、リピート間隔が短くなります。
ここはそのうち作り直したほうが良いかなあと思います・・・。
(私は、IMA ADPCMは音の音の長さが、ヘッダに書いてあるものとどうも違ってしまっていて(どうも読み方が分からない・・・)、正しい長さが分からなくて、リピート部分がヘンテコになっています。DCC館などにヘッダの説明がありますので、だれか確認していただければと思います。)
リピート時にgSound_playnextに音を切り替えます。
電圧は、Timer1.setPwmDuty(VVVF_SOUND_PIN,t);で、t:0〜1023の10ビットで出します。
  • byte readSoundRom(uint8_t inNo, uint16_t inOffset)
→ROMからサンプリング値を取り出します。inNoが音番号、inOffsetが実際のデータの番号です。
  • uint16_t ima_decode( /* Returns the sample value (-32768 to 32767) / Sound extension by MECY 2017/01/14 */ IMADEC* ad, /* Work area for the ADPCM stream */ uint8_t dat, /* ADPCM data, b3:sign (0:+, 1:-), b2-b0:magnify (0..7) */ int8_t hl /* High or low data read */ )
→IMA ADPCMのデコード部分です。コピペですので、説明は省略します。

サンプリング音源の作り方

 まずはディーゼル音を録ってきます。
 編集はAudacityで行います。(Audacityの操作方法はネットで探してください。)
(1)音ファイルをAudacityで開きます。
(2)トラック→ステレオからモノラルへ を選んで、モノラルデータにします。
(3)適当なディーゼル音部分を切り出して、リピート再生させて、変じゃない部分を探します。

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

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

 なお、私のサンプリングファイルではデータ数が配列で3500程度になっていました。(画像は別のものです)

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

管理人/副管理人のみ編集できます