
Realtime sound spectrogram using FFT or linear prediction. Spectrogram is displayed on the display of PC. リアルタイム・スペクトログラム.解析の手法:FFT,線形予測法.スペクトログラムは PC のディスプレー装置に表示される.PC 側のプログラム:F446_Spectrogram.
Dependencies: Array_Matrix mbed SerialTxRxIntr F446_AD_DA UIT_FFT_Real
Revision 0:a539141b9dec, committed 2017-02-17
- Comitter:
- MikamiUitOpen
- Date:
- Fri Feb 17 04:55:10 2017 +0000
- Child:
- 1:cc596a8d40c9
- Commit message:
- 1
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Array_Matrix.lib Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/MikamiUitOpen/code/Array_Matrix/#a25dba17218c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/F446_AD_DA.lib Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/MikamiUitOpen/code/F446_AD_DA/#d1da91aec62f
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/AnalyzerBase.cpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,27 @@ +//------------------------------------------------------- +// Base abstract class for spectrum analysis +// +// 2017/02/09, Copyright (c) 2017 MIKAMI, Naoki +//------------------------------------------------------- + +#include "AnalyzerBase.hpp" + +namespace Mikami +{ + AnalyzerBase::AnalyzerBase(int nData, int nFft, int nUse) + : N_DATA_(nData), N_FFT_(nFft), + fft_(nFft), wHm_(nData-1, nUse), b1_(1.0f), + xData_(nUse), wData_(nUse) {} + + void AnalyzerBase::Execute(const float xn[], float db[]) + { + // 高域強調 + for (int n=0; n<N_DATA_-1; n++) + xData_[n] = xn[n+1] - b1_*xn[n]; + + // 窓掛け + wHm_.Execute(xData_, wData_); + + Analyze(wData_, db); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/AnalyzerBase.hpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,52 @@ +//------------------------------------------------------- +// Base abstract class for spectrum analysis (Header) +// +// 2017/02/09, Copyright (c) 2017 MIKAMI, Naoki +//------------------------------------------------------- + +#ifndef BASE_ANALYZER_HPP +#define BASE_ANALYZER_HPP + +#include "Array.hpp" +#include "fftReal.hpp" +#include "Hamming.hpp" + +namespace Mikami +{ + class AnalyzerBase + { + public: + // nData: Number of data to be analyzed + // nFft: Number of FFT points + // nUse: FFT, cepstrum: window width + zero padding + // Linear prediction: window width + AnalyzerBase(int nData, int nFft, int nUse); + virtual ~AnalyzerBase() {} + void Execute(const float xn[], float db[]); + // 高域強調の程度を決める定数の設定(b1 = 1 で差分,b1 = 0 で高域強調なし) + void SetHighEmphasizer(float b1) { b1_ = b1; } + + protected: + const int N_DATA_; + const int N_FFT_; + + FftReal fft_; + + float Norm(Complex x) + { return x.real()*x.real() + x.imag()*x.imag(); } + + private: + HammingWindow wHm_; + float b1_; + + Array<float> xData_; // data to be analyzed + Array<float> wData_; // windowd data + + virtual void Analyze(const float wData[], float db[]) = 0; + + // disallow copy constructor and assignment operator + AnalyzerBase(const AnalyzerBase& ); + AnalyzerBase& operator=(const AnalyzerBase& ); + }; +} +#endif // BASE_ANALYZER_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/FFT_Analyzer.cpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,23 @@ +//------------------------------------------------------- +// Class for spectrum analysis using FFT +// +// 2017/02/04, Copyright (c) 2017 MIKAMI, Naoki +//------------------------------------------------------- + +#include "FFT_Analyzer.hpp" + +namespace Mikami +{ + FftAnalyzer::FftAnalyzer(int nData, int nFft) + : AnalyzerBase(nData, nFft, nFft), + yFft_(nFft/2+1) {} + + void FftAnalyzer::Analyze(const float xn[], float yn[]) + { + fft_.Execute(xn, yFft_); // Execute FFT + + // Translate to dB + for (int n=0; n<=N_FFT_/2; n++) + yn[n] = 10.0f*log10f(Norm(yFft_[n])); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/FFT_Analyzer.hpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,31 @@ +//------------------------------------------------------- +// Class for spectrum analysis using FFT (Header) +// +// 2017/02/04, Copyright (c) 2017 MIKAMI, Naoki +//------------------------------------------------------- + +#ifndef FFT_ANALYZER_HPP +#define FFT_ANALYZER_HPP + +#include "AnalyzerBase.hpp" + +namespace Mikami +{ + class FftAnalyzer : public AnalyzerBase + { + public: + FftAnalyzer(int nData, int nFft); + virtual ~FftAnalyzer() {} + + private: + Array<Complex> yFft_; // output of FFT + + virtual void Analyze(const float xn[], float yn[]); + + // disallow copy constructor and assignment operator + FftAnalyzer(const FftAnalyzer& ); + FftAnalyzer& operator=(const FftAnalyzer& ); + }; +} + +#endif // FFT_ANALYZER_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/Hamming.hpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,48 @@ +//------------------------------------------------------------------- +// Hamming windowing with zero-padding +// +// 2016/07/23, Copyright (c) 2016 MIKAMI, Naoki +//------------------------------------------------------------------- + +#ifndef HAMMING_WINDOW_HPP +#define HAMMING_WINDOW_HPP + +#include "mbed.h" +#include "Array.hpp" + +namespace Mikami +{ + class HammingWindow + { + public: + // Constructor + HammingWindow(uint16_t nData, uint16_t nFft) + : N_(nData), NFFT_(nFft), w_(nData) + { + float pi2L = 6.283185f/(float)nData; + for (int k=0; k<nData; k++) + w_[k] = 0.54f - 0.46f*cosf(k*pi2L); + } + + // Destructor + virtual ~HammingWindow() {} + + // Windowing + void Execute(const float x[], float y[]) + { + for (int n=0; n<N_; n++) y[n] = x[n]*w_[n]; + for (int n=N_; n<NFFT_; n++) y[n] = 0; + } + + private: + const int N_; + const int NFFT_; + + Array<float> w_; + + // disallow copy constructor and assignment operator + HammingWindow(const HammingWindow& ); + HammingWindow& operator=(const HammingWindow& ); + }; +} +#endif // HAMMING_WINDOW_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/LPC_Analyzer.cpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,31 @@ +//------------------------------------------------------- +// Class for spectrum analysis using linear prediction +// +// 2017/02/11, Copyright (c) 2017 MIKAMI, Naoki +//------------------------------------------------------- + +#include "LPC_Analyzer.hpp" + +namespace Mikami +{ + LpcAnalyzer::LpcAnalyzer(int nData, int nFft, int order) + : AnalyzerBase(nData, nFft, nData-1), order_(order), + lp_(nData-1, order), + an_(order), xFft_(nFft), yFft_(nFft/2+1) {} + + void LpcAnalyzer::Analyze(const float xn[], float yn[]) + { + float em; + lp_.Execute(xn, an_, em); + + // To spectrum + xFft_[0] = 1.0f; + for (int n=0; n<order_; n++) xFft_[n+1] = -an_[n]; + for (int n=order_+1; n<N_FFT_; n++) xFft_[n] = 0.0f; + fft_.Execute(xFft_, yFft_); // execute FFT + + // Translate to dB + for (int n=0; n<=N_FFT_/2; n++) + yn[n] = 10.0f*log10f(em/Norm(yFft_[n])); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/LPC_Analyzer.hpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,36 @@ +//--------------------------------------------------------------- +// Class for spectrum analysis using linear prediction (Header) +// +// 2017/02/11, Copyright (c) 2017 MIKAMI, Naoki +//--------------------------------------------------------------- + +#ifndef LPC_ANALYZER_HPP +#define LPC_ANALYZER_HPP + +#include "AnalyzerBase.hpp" +#include "LinearPrediction.hpp" + +namespace Mikami +{ + class LpcAnalyzer : public AnalyzerBase + { + public: + LpcAnalyzer(int nData, int nFft, int order); + virtual ~LpcAnalyzer() {} + + private: + int order_; + LinearPred lp_; + + Array<float> an_; // predictor coefficient + Array<float> xFft_; // input for FFT + Array<Complex> yFft_; // output of FFT + + virtual void Analyze(const float xn[], float yn[]); + + // disallow copy constructor and assignment operator + LpcAnalyzer(const LpcAnalyzer& ); + LpcAnalyzer& operator=(const LpcAnalyzer& ); + }; +} +#endif // LPC_ANALYZER_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/LinearPrediction.cpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,62 @@ +//----------------------------------------------------- +// Class for linear prediction +// +// 2017/02/11, Copyright (c) 2017 MIKAMI, Naoki +//----------------------------------------------------- + +#include "LinearPrediction.hpp" + +namespace Mikami +{ + LinearPred::LinearPred(int nData, int order) + : N_DATA_(nData), order_(order), + r_(order+1), k_(order), am_(order) {} + + // Calculate linear-predictive coefficients + bool LinearPred::Execute(const float x[], float a[], + float &em) + { + AutoCorr(x); + return Durbin(a, em); + } + + // Calculate auto-correlation + void LinearPred::AutoCorr(const float x[]) + { + for (int j=0; j<=order_; j++) + { + r_[j] = 0.0; + for (int n=0; n<N_DATA_-j; n++) + r_[j] = r_[j] + x[n]*x[n+j]; + } + } + + // Levinson-Durbin algorithm + bool LinearPred::Durbin(float a[], float &em) + { + // Initialization + em = r_[0]; + + // Repeat + for (int m=0; m<order_; m++) + { + float w = r_[m+1]; + for (int j=0; j<=m-1; j++) + w = w - r_[m-j]*a[j]; + + k_[m] = w/em; + em = em*(1 - k_[m]*k_[m]); + + if (em < 0) break; // Error for negative squared sum of residual + + a[m] = k_[m]; + for (int j=0; j<=m-1; j++) + am_[j] = a[j]; + for (int j=0; j<=m-1; j++) + a[j] = am_[j] - k_[m]*am_[m-j-1]; + } + + if (em < 0) return false; + else return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MySpectrogram/LinearPrediction.hpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,37 @@ +//----------------------------------------------------- +// Class for linear prediction (Header) +// +// 2017/02/11, Copyright (c) 2017 MIKAMI, Naoki +//----------------------------------------------------- + +#ifndef LINEAR_PREDICTION_HPP +#define LINEAR_PREDICTION_HPP + +#include "Array.hpp" + +namespace Mikami +{ + class LinearPred + { + public: + LinearPred(int nData, int order); + ~LinearPred() {} + bool Execute(const float x[], float a[], float &em); + private: + const uint16_t N_DATA_; + + uint16_t order_; + + Array<float> r_; // for auto-correlation + Array<float> k_; // for PARCOR coefficients + Array<float> am_; // working area + + void AutoCorr(const float x[]); + bool Durbin(float a[], float &em); + + // disallow copy constructor and assignment operator + LinearPred(const LinearPred& ); + LinearPred& operator=(const LinearPred& ); + }; +} +#endif // LINEAR_PREDICTION_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UIT_FFT_Real.lib Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/MikamiUitOpen/code/UIT_FFT_Real/#9649d0e2bb4a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,192 @@ +//--------------------------------------------------------------------- +// スペクトログラム (Nucleo-F446RE 用),条件等を PC から受け取るテスト +// +// ● ST-Link Firmware の V2.J28.M16 で動作確認 +// +// ● ST-Link Firmware のアップグレードには stsw-link07.zip +// に含まれている "ST-LinkUpgrade.exe" を使う +// +// ● PC 側のプログラム: "F446_Spectrogram" +// ● ボーレート: 460800 baud +// ● 受信データの文字列の終了マーク: "\r" +// +// 2017/02/17, 三上 直樹 +//--------------------------------------------------------------------- + +#include "mbed.h" +#include <string> +#include "myFunctions.hpp" +#include "Array.hpp" +#include "F446_ADC_Interrupt.hpp" +#include "FFT_Analyzer.hpp" +#include "LPC_Analyzer.hpp" +using namespace Mikami; + +#ifndef __STM32F446xx_H +#error "Use Nucleo-F446RE" +#endif + +const int N_FFT_ = 512; // FFT の点数 +const int N_DATA_ = N_FFT_ + 1; // スペクトル解析に使うデータ数(差分処理を考慮) +const int N_FRAME_ = N_FFT_/2 + 1; // 1フレーム当たり標本化するデータ数 +const int N_FFT_2_ = N_FFT_/2; // FFT の点数の半分 +const float AMP_ = 1.0f/2048.0f; // uint16_t 型のデータを float 型に変換する際の定数 + +uint16_t xPing_[N_FRAME_]; // 標本化したデータのバッファ1 +uint16_t xPong_[N_FRAME_]; // 標本化したデータのバッファ2 +uint16_t *inPtr_ = xPing_; // AD 変換データの格納先を指すポインタ +uint16_t *outPtr_ = xPing_; // 取り出すデータを指すポインタ + +__IO int inCount_ = 0; // 入力データのカウンタ +__IO int pingPong_ = 0; // 入力データの格納先,0: xPing_[], 1: xPong_[] +__IO bool full_ = false; // AD 変換データが満杯のとき true + +const int FS_ = 16000; // 標本化周波数: 16 kHz +AdcDual_Intr myAdc_(FS_); // "F446_ADC_Interrupt.hpp" で定義 +DacDual myDac_; // "F446_DAC" で定義 + +// FFT によるスペクトル解析オブジェクトの生成 +FftAnalyzer *fftAnlz_ = new FftAnalyzer(N_DATA_, N_FFT_); +// 線形予測法 によるスペクトル解析オブジェクトの生成 +LpcAnalyzer *lpcAnlz_ = new LpcAnalyzer(N_DATA_, N_FFT_, 20); +AnalyzerBase *analyzer_ = fftAnlz_; + +Serial pc_(USBTX, USBRX); // PC との通信で使うオブジェクト +DigitalOut myLed_(D10, 1); // LED1 が使えないので D10 を使う + +DigitalOut pulse(D2, 0); // 時間測定用 + +const int DATA_SIZE_ = N_FFT_/2 + 1; +Array<int16_t> txData_(DATA_SIZE_); // 送信用データ +string rxBuffer_; // 受信バッファ +float levelShift_ = 20; // dB 計算の際のシフト量の初期値 +float empha_ = 0.8f; // 高域強調器の係数 + +__IO bool eol_; // "\r" を受信した場合に true + +// 入力チャンネルを選択する関数とそれを割り当てる関数ポインタ +float InputL(float x1, float x2) { return x1; } +float InputR(float x1, float x2) { return x2; } +float InputLR(float x1, float x2) { return (x1 + x2)/2; } +typedef float (*FP_INPUT)(float, float); +FP_INPUT InputCurrent = InputLR; // 最初は左右チャンネルを使う +FP_INPUT InputNew = InputCurrent; + +// ADC 変換終了割り込みに対する割り込みサービス・ルーチン +void AdcIsr() +{ + uint16_t sn1, sn2; + myAdc_.Read(sn1, sn2); + uint16_t xn = InputCurrent(sn1, sn2); + inPtr_[inCount_] = xn; + myDac_.Write(xn, xn); + + if (++inCount_ >= N_FRAME_) // データが満杯か調べる + { + full_ = true; // データが満杯 + inCount_ = 0; // 以降のデータ取得のため + pingPong_ = (pingPong_+1) & 0x01; // バッファの切り替えのため + inPtr_ = (pingPong_ == 0) ? xPing_ : xPong_; // バッファのポインタ指定 + InputCurrent = InputNew; // 入力の切り替え + analyzer_->SetHighEmphasizer(empha_); // 高域強調の有無の指令 + } +} + +int main() +{ + float sn[N_DATA_]; // スペクトル解析の対象となるデータ + float db[N_FRAME_]; // 解析結果である対数スペクトル [dB] + for (int n=0; n<N_DATA_; n++) sn[n] = 0; + for (int n=0; n<N_FRAME_; n++) xPong_[n] = 2048; // uint16_t 型の 0 に対応 + + rxBuffer_ = ""; // 受信バッファのクリア + eol_ = false; + + pc_.baud(115200*4); // ボーレートの設定 + pc_.format(); // default: 8 bits, nonparity, 1 stop bit + + NVIC_SetPriority(ADC_IRQn, 1); // AD変換終了割り込みの優先度が最高 + NVIC_SetPriority(USART2_IRQn, 2); + + pc_.attach(&Rx); // 受信割り込みの割り当て + + full_ = false; + myAdc_.SetIntrVec(&AdcIsr); // AD変換終了割り込みの割り当て + + __IO bool ready = false; // スペクトルの計算終了で true + __IO bool okGo = false; // "GO" を受信したら true + while (true) + { + // PC からのコマンド解析 + if (eol_) + { + if (rxBuffer_.find("ENQ") != string::npos) + pc_.printf("ACK\n"); // "ACK" を PC へ転送 + else if (rxBuffer_.find("GO") != string::npos) + { + // rxBuffer_ の内容 + // [0] 'G' + // [1] 'O' + // [2] 入力チャンネルの選択:'L', 'R', or '+' + // [3] スペクトルの値のレベルシフト:' ' ~ 'I' が -20 ~ 20 に対応 + // [4] 高域強調器の有無:'Y', 'N' + // [5] 解析方法 F: FFT,L: 線形予測法 + + switch (rxBuffer_[2]) // 'L', 'R', or '+' + { + case 'L': InputNew = InputL; break; + case 'R': InputNew = InputR; break; + case '+': InputNew = InputLR; break; + default : InputNew = InputLR; break; + } + + levelShift_ = (float)(rxBuffer_[3] - ' '); // dB 計算の際のシフト量 + + if (rxBuffer_[4] == 'Y') empha_ = 0.8f; // 高域強調器は有 + else empha_ = 0; // 高域強調器は無 + + if (rxBuffer_[5] == 'F') analyzer_ = fftAnlz_; // FFT + else analyzer_ = lpcAnlz_; // 線形予測法 + + okGo = true; // データの転送要求あり + } + + eol_ = false; + rxBuffer_ = ""; // 受信バッファのクリア + wait_ms(1); + } + + if (full_) // 入力データが満杯かどうか調べる + { + full_ = false; + + outPtr_ = (pingPong_ == 1) ? xPing_ : xPong_; + // フレームの後半のデータを前半に移動する + for (int n=0; n<N_FFT_2_; n++) + sn[n] = sn[n+N_FRAME_]; + // フレームの後半には新しいデータを格納する + for (int n=0; n<N_FRAME_; n++) + sn[n+N_FFT_2_] = AMP_*(outPtr_[n] - 2048); + + analyzer_->Execute(sn, db); // スペクトル解析の実行 + + const float FACTOR = 4095.0f/60.0f; // 表示範囲: 0 ~ 60 dB + for (int n=0; n<DATA_SIZE_; n++) + { + int16_t spc = (int16_t)(FACTOR*(db[n] + 30.0f + levelShift_)); + if (spc > 4095) spc = 4095; + if (spc < 0) spc = 0; + txData_[n] = spc; + } + ready = true; // スペクトル解析終了 + } + + // 転送要求がありスペクトル解析が終了している場合にデータを PC へ転送する + if (okGo && ready) + { + Xfer(txData_); // データを PC へ転送 + ready = false; + okGo = false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed/builds/ef9c61f8c49f \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/myFunctions.cpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,31 @@ +#include "myFunctions.hpp" + +// シリアル・ポートの受信割り込み +void Rx() +{ + unsigned char chr = pc_.getc(); + // '\r' を受信した場合はメッセージの終了とする + // '\r' は,rxBuffer_ には追加されない + if (chr == '\r') eol_ = true; + else rxBuffer_ += chr; // '\r' が来るまで文字が追加される + + // 受信するメッセージの文字数のチェック + if (rxBuffer_.size() > RX_MAX_) + while (true) // 文字数がオーバーの場合 LED が点滅する + { + myLed_ = !myLed_; + wait(0.1f); + } +} + +// データを PC へ転送(12 ビットを 2 文字で表すコード化を利用) +void Xfer(Array<int16_t> &xn) +{ + for (int n=0; n<xn.Length(); n++) + pc_.printf("%c%c", ((xn[n] & 0xFC0) >> 6) + ' ', + (xn[n] & 0x3F) + ' '); + + pc_.printf("\n"); // データの最後を通知 + wait_ms(1); // これは必須 + pc_.printf("EOT\n"); // 転送終了であることを送信 +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/myFunctions.hpp Fri Feb 17 04:55:10 2017 +0000 @@ -0,0 +1,22 @@ +#include "mbed.h" +#include <string> +#include "Array.hpp" +using namespace Mikami; + +#ifndef MY_FUNCTIONS_HPP +#define MY_FUNCTIONS_HPP + +const int RX_MAX_ = 32; // 受信バッファの文字数の最大値 + +extern string rxBuffer_; // 受信バッファ +extern __IO bool eol_; // "\r" を受信した場合に true + +extern Serial pc_; +extern DigitalOut myLed_; + +// シリアル・ポートの受信割り込み +void Rx(); +// データを PC へ転送 +void Xfer(Array<int16_t> &xn); + +#endif // MY_FUNCTIONS_HPP