SPRESENSE でスペクトログラム出力をしてみた [SPRESENSE]
前回、Python でスペクトログラム出力を検討しましたが、今回は SPRESENSE でスペクトログラム出力ができるようにしました!
画質は悪いですが、動作している様子をTwitterにあげました。そのうちYoutubeにもあげたいと思います!
このプログラムは、最近新しく追加されたSPRESENSEの Signal Processing ライブラリを使いました。もう少しリファインが必要ですがコードを晒します。表示を少しでも早くするために、信号処理と表示をサブスコアを使って並行処理にしています。
こんな芸当ができるのも、SPRESENSEならではですね。(^^)/~
■ メインコアのスケッチ
■ サブスコアのスケッチ
画質は悪いですが、動作している様子をTwitterにあげました。そのうちYoutubeにもあげたいと思います!
Spresense の Spectrogram出力。表示を少し高速化(とは言っても20msecは大きい)。いい感じで出力できるようになってきた。ただ、時々線が出ているのが気になる。もう少し解析が必要だな。 pic.twitter.com/uSbYSpPO7M
— よしのたろう (@Taro_Yoshino) August 4, 2020
このプログラムは、最近新しく追加されたSPRESENSEの Signal Processing ライブラリを使いました。もう少しリファインが必要ですがコードを晒します。表示を少しでも早くするために、信号処理と表示をサブスコアを使って並行処理にしています。
こんな芸当ができるのも、SPRESENSEならではですね。(^^)/~
■ メインコアのスケッチ
#ifdef SUBCORE #error "Core selection is wrong!!" #endif #include <MP.h> #include <MPMutex.h> #include <pthread.h> #define SUBCORE1 1 MPMutex mutex(MP_MUTEX_ID0); pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; #include <Audio.h> #include <FFT.h> #include <IIR.h> #define FFT_LEN 1024 #define SMA_WINDOW 8 #define CHANNEL_NUM 1 #define LPF_CUTOFF 3000 #define LPF_QVALUE 0.70710678 FFTClass<CHANNEL_NUM, FFT_LEN> FFT; IIRClass LPF; AudioClass *theAudio = AudioClass::getInstance(); static const int32_t buffer_sample = FFT_LEN; static const int32_t buffer_size = buffer_sample * sizeof(int16_t); static char buff[buffer_size]; uint32_t read_size; static float pDst[FFT_LEN]; static float pOut[FFT_LEN/2]; static float pSMA[SMA_WINDOW][FFT_LEN]; void applySMA(float sma[SMA_WINDOW][FFT_LEN], float dst[FFT_LEN]) { int i, j; static int g_counter = 0; if (g_counter == SMA_WINDOW) g_counter = 0; for (i = 0; i < FFT_LEN; ++i) { sma[g_counter][i] = dst[i]; float sum = 0; for (j = 0; j < SMA_WINDOW; ++j) { sum += sma[j][i]; } dst[i] = sum / SMA_WINDOW; } ++g_counter; } static void audioReadFrames() { int err, ret; static int g_counter = 0; static q15_t pLPFSig[FFT_LEN]; while(1) { err = theAudio->readFrames(buff, buffer_size, &read_size); if (err != AUDIOLIB_ECODE_OK && err != AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) { Serial.println("Error err = " + String(err)); theAudio->stopRecorder(); exit(1); } if (read_size < buffer_size) { usleep(10000); continue; } // 3kHz low pass filter LPF.put((q15_t*)buff, FFT_LEN); LPF.get(pLPFSig, 0); // FFT FFT.put(pLPFSig, FFT_LEN); // Using mutex to protect pDst array if (pthread_mutex_lock(&m) != 0) Serial.println("Mutex Lock Error"); FFT.get(pDst, 0); applySMA(pSMA, pDst); if (pthread_mutex_unlock(&m) != 0) Serial.println("Mutex UnLock Error"); usleep(8000); } } void setup() { Serial.begin(115200); MP.begin(SUBCORE1); MP.RecvTimeout(MP_RECV_POLLING); LPF.begin(TYPE_LPF, CHANNEL_NUM, LPF_CUTOFF, LPF_QVALUE); FFT.begin(WindoHanning, 1, (FFT_LEN/4)); theAudio->begin(); theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC, 200); int err = theAudio->initRecorder(AS_CODECTYPE_PCM ,"/mnt/sd0/BIN" ,AS_SAMPLINGRATE_48000 ,AS_CHANNEL_MONO); if (err != AUDIOLIB_ECODE_OK) { Serial.println("Recorder initialize error"); while(1); } theAudio->startRecorder(); Serial.println("Start Recording"); task_create("audio recording", 120, 1024, audioReadFrames, NULL); sleep(1); } void loop() { int err, ret; int8_t sndid; // Using mutex to protect pDst array if (pthread_mutex_lock(&m) != 0) Serial.println("Mutex Lock Error"); memcpy(pOut, pDst, buffer_size /2); if (pthread_mutex_unlock(&m) != 0) Serial.println("Mutex UnLock Error"); // Using MPMutex to check the availablity of SubCore if (mutex.Trylock() != 0) { usleep(20000); return; } sndid = 100; err = MP.Send(sndid, &pOut, SUBCORE1); if (err < 0) Serial.println("MP Send error\n"); mutex.Unlock(); }
■ サブスコアのスケッチ
#if (SUBCORE != 1) #error "Core selection is wrong!!" #endif #include <MP.h> #include <MPMutex.h> #include <Adafruit_GFX.h> #include <Adafruit_ILI9341.h> #define CS 10 #define DC 9 #define RST 8 Adafruit_ILI9341 tft = Adafruit_ILI9341(CS, DC, RST); MPMutex mutex(MP_MUTEX_ID0); #define FFT_LEN 1024 #define SPECTRO_WIDTH (FFT_LEN/8) #define SPECTRO_HEIGHT (320) static uint16_t frameBuffer[SPECTRO_HEIGHT][SPECTRO_WIDTH]; void setup() { tft.begin(); tft.setRotation(3); tft.fillScreen(ILI9341_BLACK); tft.setCursor(35, 210); tft.setTextColor(ILI9341_WHITE); tft.setTextSize(2); tft.println("FFT Spectrogram Viewer"); tft.setRotation(2); memset(frameBuffer, 255, SPECTRO_WIDTH*SPECTRO_HEIGHT*sizeof(uint16_t)); MP.begin(); MP.RecvTimeout(MP_RECV_POLLING); } void loop() { // based on CXD5247 technical manual static const float spr_signal_noise_ratio = 90.0; // SNR static uint16_t colormap[] = { ILI9341_MAGENTA, ILI9341_BLUE, ILI9341_CYAN, ILI9341_GREEN, ILI9341_YELLOW, ILI9341_ORANGE, ILI9341_RED, }; int8_t msgid; float *data; int ret, i, j; ret = MP.Recv(&msgid, &data); if (ret < 0) return; // Using MPMutex to notify MainCore that SubCore is in busy do { ret = mutex.Trylock(); } while (ret != 0); for (i = 0; i < FFT_LEN/2; ++i) { if (!isnan(data[i]) && data[i] > 0.0) { data[i] = 20.*log10(data[i]) + spr_signal_noise_ratio; } else { data[i] = 0.0; // under the noise level? } } for (i = 1; i < SPECTRO_HEIGHT; ++i) { for (j = 0; j < SPECTRO_WIDTH; ++j) { frameBuffer[i-1][j] = frameBuffer[i][j]; } } // display range:0:0Hz - FFT_LEN/8(128):6kHz static const float magnify = 1.0; // To magnify the signal for (i = 0; i < SPECTRO_WIDTH; ++i) { uint8_t index = magnify*data[i]/32; frameBuffer[SPECTRO_HEIGHT-1][i] = colormap[index]; } tft.drawRGBBitmap(40, 0, (uint16_t*)frameBuffer, SPECTRO_WIDTH, SPECTRO_HEIGHT); mutex.Unlock(); }
サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)
- 作者: 青木 直史
- 出版社/メーカー: 技術評論社
- 発売日: 2013/02/01
- メディア: 単行本(ソフトカバー)
SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware