SPRESENSEスペクトログラムをアップデート!! [SPRESENSE]
SPRESENSEスペクトログラムをアップデートしました。今回は微妙な変化がわかるようにグレースケールで表示してみました。あとピーク周波数を出すようにしています。
実際の動きはこちらをご覧ください。
スケッチはこちら。解析はメインコアで、表示はサブスコアで行っています。MP.Mutex でサブスコアにデータを送るタイミングをとっています。
■ メインコアのスケッチ
■ サブコアのスケッチ
次はどうやって学習データを記録していくか考えたいと思います。
(。-`ω´-)ンー
実際の動きはこちらをご覧ください。
Spresense Spectrogram アップデートしました。
— よしのたろう (@Taro_Yoshino) August 18, 2020
FFTのポイント数は256~4096まで対応してますが、LCDの画面の都合上256にしました。Spresenseの計算能力はまだまだ余裕があるので、もっと高解像度いけそうです。 pic.twitter.com/6VNqNAttKA
スケッチはこちら。解析はメインコアで、表示はサブスコアで行っています。MP.Mutex でサブスコアにデータを送るタイミングをとっています。
■ メインコアのスケッチ
#ifdef SUBCORE #error "Core selection is wrong!!" #endif #include#include #include #define SUBCORE1 1 MPMutex mutex(MP_MUTEX_ID0); pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; #include #include #include #define FFT_LEN 256 #define CHANNEL_NUM 1 // #define LPF_ENABLE #define LPF_CUTOFF 3000 #define LPF_QVALUE 0.70710678 // #define SMA_ENABLE #define SMA_WINDOW 8 FFTClass 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 const uint32_t sampling_rate = AS_SAMPLINGRATE_48000; static char buff[buffer_size]; static float pDst[FFT_LEN]; static float pOut[FFT_LEN]; #ifdef SMA_ENABLE 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; } #endif static void audioReadFrames() { int err, ret; uint32_t read_size; static bool bInit = true; static q15_t pLPFSig[buffer_size]; static const unsigned long wait_usec = (double)(buffer_sample)/sampling_rate*1000000; Serial.println(wait_usec); 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(wait_usec); continue; } #ifdef LPF_ENABLE // 3kHz low pass filter LPF.put((q15_t*)buff, buffer_sample); LPF.get(pLPFSig, 0); FFT.put(pLPFSig, buffer_sample); #else FFT.put((q15_t*)buff, buffer_sample); #endif if (bInit) { bInit = false; continue; } // Using mutex to protect pDst array if (pthread_mutex_lock(&m) != 0) Serial.println("Mutex Lock Error"); FFT.get(pDst, 0); #ifdef SMA_ENABLE applySMA(pSMA, pDst); #endif if (pthread_mutex_unlock(&m) != 0) Serial.println("Mutex UnLock Error"); } } void setup() { int8_t sndid; Serial.begin(115200); MP.begin(SUBCORE1); sndid = 101; MP.Send(sndid, FFT_LEN, SUBCORE1); MP.RecvTimeout(MP_RECV_POLLING); #ifdef LPF_ENABLE LPF.begin(TYPE_LPF, CHANNEL_NUM, LPF_CUTOFF, LPF_QVALUE); #endif FFT.begin(WindowRectangle, CHANNEL_NUM, (FFT_LEN/2)); theAudio->begin(); // theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC, 200); theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC); 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); 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 #define ARM_MATH_CM4 #define __FPU_PRESENT 1U #include#include #include #include #include #include #define CS 10 #define DC 9 #define RST 8 Adafruit_ILI9341 tft = Adafruit_ILI9341(CS, DC, RST); MPMutex mutex(MP_MUTEX_ID0); #define SPECTRO_WIDTH (128) #define SPECTRO_HEIGHT (320) static uint16_t frameBuffer[SPECTRO_HEIGHT][SPECTRO_WIDTH]; #define DEBUG // #define PSEUDO_COLOR static uint16_t fft_len = 0; float get_peak_frequency(float *pData, int fftLen) { float g_fs = 48000.0f; uint32_t index; float maxValue; float delta; float peakFs; arm_max_f32(pData, fftLen / 2, &maxValue, &index); delta = 0.5 * (pData[index - 1] - pData[index + 1]) / (pData[index - 1] + pData[index + 1] - (2.0f * pData[index])); peakFs = (index + delta) * g_fs / (fftLen - 1); return peakFs; } void setup() { int8_t recvid; uint32_t msg; 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.setTextColor(ILI9341_BLUE); tft.setTextSize(2); tft.setCursor(35, 35); tft.print("Peak FS:"); tft.setCursor(210, 35); tft.print("Hz"); tft.setRotation(2); memset(frameBuffer, 255, SPECTRO_WIDTH*SPECTRO_HEIGHT*sizeof(uint16_t)); MP.begin(); MP.Recv(&recvid, &msg); fft_len = msg; MPLog("%d FFT_LEN %d\n", recvid, fft_len); 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, ILI9341_WHITE }; int8_t msgid; float *data; int ret, i, j; float f_max, f_min; 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); #ifdef DEBUG f_max = 0.0; f_min = 1000.0; for (i = 0; i < fft_len/2; ++i) { float val = abs(data[i]); // MPLog("[%d] %f\n", i, val); if (val == 0.0) continue; if (val > f_max) f_max = val; if (val < f_min) f_min = val; } MPLog("RANGE: %3.6f - %+3.6f\n", f_max, f_min); #endif f_max = -1000.0; f_min = 1000.0; for (i = 0; i < fft_len/2; ++i) { // if (!isnan(data[i]) && data[i] > 0.0) { if (data[i] != 0.0) { data[i] = 20.*log10(abs(data[i])) + spr_signal_noise_ratio; if (data[i] > f_max) f_max = data[i]; if (data[i] < f_min) f_min = data[i]; } } MPLog("Range: %3.6f - %3.6f\n\n", f_max, f_min); 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 - 200:9.375kHz for (i = 0; i < SPECTRO_WIDTH; ++i) { float f_val = data[i]; #ifdef PSEUDO_COLOR static const float magnify = 1; uint8_t index = magnify*f_val/32; frameBuffer[SPECTRO_HEIGHT-1][i] = colormap[index]; #else uint16_t val_6, val_5; val_6 = (uint32_t)(f_val) * 64 / 256; val_5 = (uint32_t)(f_val) * 32 / 256; uint16_t val = val_5 << 11 | val_6 << 5 | val_5; frameBuffer[SPECTRO_HEIGHT-1][i] = val; #endif } tft.drawRGBBitmap(40, 0, (uint16_t*)frameBuffer, SPECTRO_WIDTH, SPECTRO_HEIGHT); uint16_t peak_fs = get_peak_frequency(data, fft_len); static int g_counter = 0; if (g_counter % 10 == 0) { char num_buf[8]; tft.setRotation(3); tft.setTextColor(ILI9341_BLUE); tft.setTextSize(2); tft.setCursor(140, 35); tft.fillRect(140, 35, 60, 15, ILI9341_BLACK); sprintf(num_buf, "%05d", peak_fs); tft.print(num_buf); tft.setRotation(2); g_counter = 1; } else { ++g_counter; } mutex.Unlock(); }
次はどうやって学習データを記録していくか考えたいと思います。
(。-`ω´-)ンー
SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
Voice Assistant Using SONY Spresense (English Edition)
- 作者: Perez, Guillermo
- 出版社/メーカー:
- 発売日: 2019/03/11
- メディア: Kindle版