Layer8 が 2025年01月31日23時36分51秒 に編集
初版
タイトルの変更
オタマトーン自動演奏 - (未完)
タグの変更
FFT
LED
mic
メイン画像の変更
記事種類の変更
製作品
Lチカの変更
ライセンスの変更
(Apache-2.0) Apache License 2.0
本文の変更
# 作品の概要 光の明度で演奏する楽器、[オタマミン(明和電機)](https://www.maywadenki.com/news/otamamin/)の自動演奏装置を目指したものです。 以前[raspberry pico向けに作成した作品](https://github.com/Layer812/keromin)のあまりに酷い音感を何とかしようと頑張りました。 ## 動作概要(目指したもの) 以下のように動作するものを目指していました。 - LEDをSpresensの高精度なPWMにより制御しオタマミンの音階を変化させる (実装) - オタマミンの演奏をアナログマイクで読み取る(実装) - FFTによる周波数解析によりPWMによる明度と音階の周波数を突合 (途中) - MIDIファイルを読み込み自動演奏(未実装) - 3台のオタマミンによる和音(未実装) ## 物理結線 LEDをオタマミンのセンサ部に、マイクをオタマミンのスピーカー部に近づけるように設置します。  ## ソース FFT処理を別コアとする必要がありましたが、手軽さを優先したことでFIFOエラーが出ています。 ```arduino:Lチカの例#include <sys/ioctl.h> #include <fcntl.h> #include <nuttx/timers/pwm.h> #include <Audio.h> AudioClass *theAudio; const int mic_channel_num = 1; #include "FFT.h" /*-----------------------------------------------------------------*/ /* * FFT parameters */ /* Select FFT length */ //#define FFT_LEN 32 //#define FFT_LEN 64 //#define FFT_LEN 128 //#define FFT_LEN 256 //#define FFT_LEN 512 #define FFT_LEN 1024 //#define FFT_LEN 2048 //#define FFT_LEN 4096 /* Number of channels*/ //#define MAX_CHANNEL_NUM 1 //#define MAX_CHANNEL_NUM 2 #define MAX_CHANNEL_NUM 1 #define SAMPLING_RATE 16000 // ex.) 48000, 16000 #define OVERLAP (FFT_LEN/2) // ex.) 0, 128, 256 FFTClass<MAX_CHANNEL_NUM, FFT_LEN> FFT; /*-----------------------------------------------------------------*/ /* * Detector parameters */ #define POWER_THRESHOLD 30 // Power #define LENGTH_THRESHOLD 30 // 20ms #define INTERVAL_THRESHOLD 100 // 100ms #define BOTTOM_SAMPLING_RATE 131 // 131Hz C3 #define TOP_SAMPLING_RATE 988 // 988kHz B5 #define FS2BAND(x) ((x)*FFT_LEN/SAMPLING_RATE) #define BOTTOM_BAND (FS2BAND(BOTTOM_SAMPLING_RATE)) #define TOP_BAND (FS2BAND(TOP_SAMPLING_RATE)) #define MS2FRAME(x) (((x)*SAMPLING_RATE/1000/(FFT_LEN-OVERLAP))+1) #define LENGTH_FRAME MS2FRAME(LENGTH_THRESHOLD) #define INTERVAL_FRAME MS2FRAME(INTERVAL_THRESHOLD) /*-----------------------------------------------------------------*/ /* Allocate the larger heap size than default */ USER_HEAP_SIZE(64 * 1024); #define PWMDEV "/dev/pwm0\0" #define PWMFREQ 2000 void setup() { Serial.begin(115200); while (!Serial); Serial.println("Init Audio Library"); theAudio = AudioClass::getInstance(); theAudio->begin(); Serial.println("Init Audio Recorder"); /* Select input device as AMIC */ theAudio->setRecorderMode(AS_SETRECDR_STS_INPUTDEVICE_MIC,0,0x20000,true); theAudio->initRecorder(AS_CODECTYPE_PCM, "/mnt/sd0/BIN", AS_SAMPLINGRATE_16000, AS_CHANNEL_MONO); Serial.println("Rec start!"); theAudio->startRecorder(); delay(100); FFT.begin(WindowHanning, 1, (FFT_LEN / 2)); } int fd = -1; void pwm_write(int freq, int duty){ struct pwm_info_s info; if(fd == -1){ fd = open(PWMDEV, O_RDONLY); }else{ ioctl(fd, PWMIOC_STOP, 0); } info.frequency = freq; info.duty = duty; ioctl(fd, PWMIOC_SETCHARACTERISTICS, (unsigned long)((uintptr_t)&info)); ioctl(fd, PWMIOC_START, 0); } #define AVG_FILTER (8) void avgFilter(float dst[FFT_LEN]) { static float pAvg[AVG_FILTER][FFT_LEN/2]; static int g_counter = 0; if (g_counter == AVG_FILTER) g_counter = 0; for(int i=0; i<FFT_LEN/2; ++i) { pAvg[g_counter][i] = dst[i]; float sum = 0; for(int j=0; j < AVG_FILTER; ++j) { sum += pAvg[j][i]; } dst[i] = sum / AVG_FILTER; } ++g_counter; } void fft(void *buffer){ int ret, i; static int pos=0; static float pDst[FFT_LEN/2]; int index; float max, peak; delay(10); FFT.put(buffer, FFT_LEN); delay(10); while(!FFT.empty(0)) FFT.get(pDst,0); arm_max_f32(pDst, FFT_LEN/2.56, &max, &index); peak = index * (SAMPLING_RATE / FFT_LEN); printf("res: peak %f, max %f ", peak, max); } int j = 10000; void loop() { int i, fd, sample = 1, pos = 0, err; static const int32_t buffer_sample = FFT_LEN; static const int32_t buffer_size = buffer_sample * sizeof(int16_t); static char buffer[2][buffer_size]; uint32_t read_size; printf("start [%d] ", j); /* Read frames to record in buffer */ for(pos = 0; pos < buffer_size;){ err = theAudio->readFrames(&(buffer[0][pos]), buffer_size - pos, &read_size); if (err != AUDIOLIB_ECODE_OK && err != AUDIOLIB_ECODE_INSUFFICIENT_BUFFER_AREA) { printf("Error err = %d\n", err); sleep(10); theAudio->stopRecorder(); exit(1); } pos += read_size; sleep(1); } memcpy(buffer[1], buffer[0], buffer_size); fft(buffer[1]); sleep(1); printf("\n"); pwm_write(PWMFREQ, j); j += 10000; if(j == 70000) j = 10000; } ``` ## 反省 作品としては完成しませんでしたが、プロジェクト未達の原因を共有することで得られることがあればと思い以下に記載します。 - 作品応募の際の技術的な条件の調査不足 GPSの位置情報とプレイリストを連動させるポータブル音楽プレイヤーを作成するというテーマを初期に掲げていたが、 拡張ボードのイヤホンジャックがモノラルであるという条件に気づかず開発を続けていた。 発覚が期限1か月前となっており、直前の無理な方針転換をしてしまった。 - 方針転換後のスケジュール考慮不足 新たなテーマを考える中で、以前作成した作品をベースにすることで時間短縮を図ろうとした。 結果的には新たな要素(アナログマイク)の調査が行き届かず、部品の再手配を数回発生させることで時間を浪費した。 - ライブラリ仕様の調査不足 録音とFFTをシリアルに実行するのであればマルチコアの処理を省略できるのでは?と甘い考えを持ってしまっていた。 期限を意識するあまりに十分な仕様調査をせずに見切り発車をしたことが原因。 ## 今後 貴重な機会を与えていただき、期待を頂いた中で期限内に達成できず、申し訳ありませんでした。 自身の学び、全国のオタマミンユーザの方に向けて、本作品の開発を継続したいと思います。