前回、Python でスペクトログラム出力を検討しましたが、今回は SPRESENSE でスペクトログラム出力ができるようにしました!





画質は悪いですが、動作している様子をTwitterにあげました。そのうちYoutubeにもあげたいと思います!





このプログラムは、最近新しく追加された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