TakSan0のアイコン画像
TakSan0 2024年01月31日作成 © CC BY-NC 4+
製作品 製作品 Lチカ Lチカ 閲覧数 282
TakSan0 2024年01月31日作成 © CC BY-NC 4+ 製作品 製作品 Lチカ Lチカ 閲覧数 282

SPRESENSEでギターマルチエフェクタを作ってみた

SPRESENSEでギターマルチエフェクタを作ってみた

はじめに

SPRESENSE があれば信号処理ができるということで、コンテストをきっかけに前からやってみたかったマルチエフェクターを作ってみました。
結論として途中あちこち躓いたので、締め切りに追われ過ぎてしまい、完全ではありませんが、できたところまでで記事を公開してコンテストに応募したいとおもいます。

システム説明

システム構成

構成図は以下の通りとしました。。

システム構成

大まかに信号処理部、UI部およびシリアル部の3つのパートで構成されます。
各部毎に説明していきます。

各部説明

信号処理部

ギターからの入力信号をプリアンプで増幅しながつつSPRESENSE内に取り込み、SPRESENSE内で信号処理をかけてアンプ出力に流すまでの一連の信号の流れです。

当初、このあたり のチュートリアルをやりつつ、内容が難しくて迷走したのですが、
SONYの太田さんが開催したと思われる Spresense ではじめるリアルタイム信号処理プログラミング の時の資料等リソースの存在を知りぐっと進みました。
プレゼン資料だけではなく、サンプルプログラムの有無は大きいですね。
数種類の信号処理のサンプルプログラムが含まれているので、まずは個別に数種類試してみたところ使えそうだったので、これを動的に切り替えれるようにする方向で対応しました。

いくつかの関数を見ると、ベース構造は全く同じで、バッファーや処理単位のサイズ ( SAMPLE_SIZE / FRAME_SIZE ) と、1つの信号処理関数 ( signal_process() ) の中身だけが違うということが判明しました。

ざっくり共通処理をひな型的に作って基底クラスを作り、異なる部分をオーバーライドする関数を作っていけばいいと考えたのですが、どうもうまくいきません。
それを実現しようとすると、インスタンスの解放して別のクラスのインスタンスを作成する必要があります。
インスタンスを解放するために処理を停止関数 ( stop() 関数 ) を呼び出して一旦止めようとすると、DMA関連の例外が発生してしまいます。
結局色々と調査をしていくうちに時間が溶けていったので、いったん諦めました。

停止することなく、処理毎に用意した signal_proces() を動的に切り替えて使う方式にしました。ここにたどり着けるまでに少し時間をとりすぎてしまい、肝心の信号処理のチューニングにあまり時間をとることはできませんでした。
そのため、一部のエフェクタはサンプルとほぼ同じ処理のままです。

現在の実装状況はこんなものです。

エフェクター種別 説明 実装した処理内容
スルー 信号処理を特に行わずそのまま出力する。 サンプルのまま。
イコライザー 低音、中音、高音の3つのレベルを個々に調整できるフィルター FFTを掛けた後の周波数成分を三分割し、それぞれの音域のレベルを設定値から読みだして係数として掛け算して逆FFTで音声に戻した信号を出力するようにしました。
ディストーション ゲインを高くして、MAXのレベルを超えた分をクリップすることで音をひずませるエフェクターです 設定パラメータのゲインを 1.0 をノーマルとした時、ゲインを10倍まで増やせるようにしています。取り込んだ信号自体にゲインを係数としてかけ合わせ、もう一つの設定値レベルの上限を超えたら最大値に張り付いてクリップされるように処理しています。
エコー 単独の信号のエコーが繰り返しながら減衰するような信号です。 サンプルのまま。
リバーブ 周りの壁や物体等いろいろなものに反響したような音の信号です サンプルのまま。
ボイスチェンジャー 入力された信号をそのまま少しピッチをずらして出力することで、別人の様な声を出すことが出来るエフェクタ サンプルのまま。

ボイスチェンジャーは、サンプルがあったので入れてみただけです。チューニングが狂った音が出ているみたいな音になるためあまり意味がありません。興味本位にどんな音が出るか試してみたまでです。

UI部

UIは全てタッチパネル付き液晶で行うようにしました。

できることは、エフェクタの種類選択と、パラメータ設定です。
画面は下の写真の様になっていて、上部がパラメータ設定部、中間部にエフェクター選択部、下部分に現在選択されているエフェクタのタイトル表示部という配置となっています。

画面構成

パラメータ設定部にあるボリュームの様なつまみの部分は、左側を押すとレベルが下がり、右側を押すとレベルが上がるようになっています。

こんな感じで現状6個のエフェクターを切り替えできます。

6種類のエフェクター

こちらも、ハードウェア部分にて制御に使う SPIを分けるわけないで迷走はした。
タッチパネル制御用と液晶表示用は別チップを使っていて、いずれもSPIバス制御です。同じ SPIバスを CS信号を分けて制御しようと試みましたが、そもそもSPIのクロック周波数が違ってどちらかを有効にするとどちらかがダメになりという状況が解決できずに時間がかかってしまいました。
色々と調べているうちに、動作クロック帯が異なるので混在は難しいという結論となり、スパッとチャンネルを分離することにしました。
SPRESENSEには 2チャンネルのSPIが2チャンネル用意されていますが、一つはメイン基板、もう一つ拡張基板となっており、メイン基板のほうを使うには 1.8Vから3.3Vへのレベル変換が必要な為面倒なので、どの端子にでも割り当てられる ソフトウェアSPIを使用することにしました。
Lovyan GFXは 高速動作が必要な個所も多いのでハードSPIを使う前提になっているようなので、低速でも問題ない タッチパネル制御側を SoftwareSPIに割り当てました。

シリアル部

こちらは BLE1507からのコマンド受け付け用のインタフェースです。
BLE は、外部デバイスからの制御用にと考えていました。フットペダルでのエフェクタの切り替えや、スマホからの切り替え制御用です。

色々と調査してこちらも時間を浪費してしまい、最後にはタイムリミットとなってしまったので今回は断念しました。
ペアリングしようとしても一覧に一切出てこず、アドバタイジング出来てい無さそうです。
最終的に信号ラインを当たると、全く信号が出ていないのでデバイスを壊してしまったのではないかと思っています。
正常に動きさえすれば、シリアル信号が入ってくるという事で、シリアルのコマンドを受け付ける様にはしています。

コマンドさえ入れれば、エフェクターの切り替えコマンドや、パラメータの設定が出来ます。

動作の様子

動作の様子を動画でご覧いただきます。
一般の曲のフレーズを引いて著作権に触れバンされたりという辺りを意識してる余裕がないので単純にコード引いてます。

動作デモ動画(YouTube)

録音環境も、使ったアンプも、ギターの状態も、私自身がギターを触るのが余りにも久しぶり過ぎということもあり、聞き苦しいところはご勘弁ください。

一応それらしき効果が出ているのと切り替えが出来る事は確認できます。

作成方法

組み立て前の写真

構成

3-1-1. 使用部品

ほぼコンテストにて提供いただいた品となりますが、以下の通りです。

名称 品名/品番 スペック/説明 備考
メイン基板 CXD5602PWBMAIN1 SPRESENSE本体ボード コンテストで提供いただいたもの
拡張基板 CXD5602PWBEXT1 SPRESENSE拡張用ボード コンテストで提供いただいたもの
BLE基板 BLE1507(BLE serialization firmware) BLEボード コンテストで提供いただいたもの
操作パネル MSP2807 タッチパネル付き液晶 コンテストで提供いただいたもの
ギタープリアンプキット GETPRE-1
DCジャック 5.1Φ/2.1Φ
6.3mm モノラルジャック 5.1Φ/2.1Φ

特別に用意したのは、コネクタや電源周り以外には、プリアンプくらいです。
プリアンプは、大阪日本橋にあるデジットが販売している デジットオリジナル ギタープリアンプ というキットです。通販でも販売しています。

実は前に試したことがあったので探し出してそのまま使おうと思ったのですが、見当たらないまま店舗が年末年始休業を迎えてしまいました。
オペアンプだけが何故か部品箱に(おそらく壊したときの予備様に買った分)残っていて、他の部品はよく使う部品だったので在庫があったので、待ちきれずに全く同じ物を自作しました。使っている部品(コンデンサの種類やボリューム等)は手持ちなので少し異なりますが定数はキットの説明書として公開されているものを使ったので、ほぼ同じスペックで、特に問題は起こりませんでした。
製作する場合はこのキットを説明書通りに組み立てる事になりますし、説明書に回路図も載っているのでアンプ自体の回路図は割愛します。

今回はキットと全く同じ物を自作しました。

回路構成

接続図

以下の図の様に接続します。

接続図

ディスプレイ - サブボード間は配線数が多いので以下の通り表にします。

拡張基板 Pin LCD Pin 信号名 備考
D03 T_IRQ タッチセンサ割り込み 未使用
D04 T_DO タッチセンサMOSI SoftSPI
D05 T_DIN タッチセンサMISO SoftSPI
D06 T_CS タッチセンサチップセレクト SoftSPI
D07 T_CLK タッチセンサクロック
D08 RESET 液晶モニタリセット
D09 DC 液晶モニタDC信号
SPI4_CS_X CS 液晶モニタチップセレクト
SPI4_MOSI SDI(MOSI) 液晶モニタMOSI HW SPI (SPI4)
SPI4_MISO SDO(MISO) 液晶モニタMISO HW SPI (SPI4)
SPI4_SCK SCK 液晶モニタクロック HW SPI (SPI4)
GND GND GROUND
AREF LED,VCC POWER 3.3V

ケース組み込み

最後にケースに組み入れます。

ケースに組み込んでいる様子

蓋表面

ジャック部分それなりに力が掛かるので、 3Dプリンタで作るとすぐに割れてしまいそうだったため、家にあった丁度いいサイズのケースに、ジャック部分だけ固定して、蓋と基板固定部分だけ 3Dプリンタでちょうどいいサイズに作りました。

それぞれ自分の好きな箱に入れるのがいいと思います。
アルミダイキャストケースに入れるとかかっこいいかもですね。

蓋部分

SPRESENSE固定台

LCDタッチパネル固定台

ハンドドリルで開けたら蓋閉めの穴が見事にずれた!の写真

ソフトウェアを書き込むまでは蓋をしない方がいいと思います。特にUSB端子から書き込むときには DCジャックからの電源は外した方が良いでしょう。

ソフトウェア

今回は、Arduino環境を使用しました。SPRESENSEは、工場出荷状態ではArduinoIDE 用のファームが書き込まれていないため、書き込んでもプログラムが走りません。
先ずは、Spresense Arduino スタートガイド を参考に、準備してください。

開発環境

ArduinoIDE 2.0 シリーズ

使用ライブラリ

以下二つのライブラリを使用しました。

  • LovyanGFXライブラリ
  • Software SPIライブラリ
  • TakSanQueue シンプルキューライブラリ(自作)

順に説明していきます。

Lovyan GFXライブラリ

液晶パネル画面病が制御には Lovyan GFX を使用しました。Arduino IDE から通常手順のライブラリインストール方法で導入できます。
LovyanGFX はターゲットマイコン毎にヘッダファイルが用意されており、そのヘッダファイルをローカル ( .ino ファイルが有る、プロジェクトを置いてあるフォルダー)に持ってきて改造するのが楽です。

ライブラリパスの中の以下の箇所に
libraries\LovyanGFX\src\lgfx_user\LGFX_SPRESENSE_sample.hpp
というファイルがあるので、それをローカルファイルにもってきて、名前を、LGFX_SPRESENSE.hpp に変更し、67行目~88行目を以下の様に修正します。

LGFX_SPRESENSE.hpp

… (中略) LGFX(void) { { // バス制御の設定を行います。 auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。 cfg.spi_mode = 0; // SPI通信モードを設定 (0 ~ 3) cfg.freq_write = 40000000; // 送信時のSPIクロック cfg.freq_read = 16000000; // 受信時のSPIクロック cfg.pin_dc = 9; // SPIのD/Cピン番号を設定 (-1 = disable) cfg.spi_port = 4; // Arduino拡張ボードの場合は 4 _bus_instance.config(cfg); // 設定値をバスに反映します。 _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。 } { // 表示パネル制御の設定を行います。 auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。 cfg.pin_cs = 10; // CSが接続されているピン番号 (-1 = disable) HW CSピンの場合は-1を指定 cfg.pin_rst = 8; // RSTが接続されているピン番号 (-1 = disable) cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable) … (中略)

Software SPIライブラリ

UIのところで述べた通りSoftwareSPIが必要なのですが、デフォルトでは SoftwareSPIは入っていない様です。
検索すると、SeeedさんWiki に、使い方と一緒に公開されているようです。

公式には ZIPからインストールする方法が掛かれていますが、試してみるとコンパイルエラーになりました。
そしてインストールすると、 SPIライブラリを使用している箇所がすべてエラーとなったのでインソールするのはやめ、ローカルに置いたうえで修正をかけてコンパイルエラー対処することにしました。

コンパイルエラーを解消するコードは以下2か所です。

  • 基底クラスのコンストラクタの初期化指定が足りない件

変更前

SoftSPI::SoftSPI(uint8_t mosi, uint8_t miso, uint8_t sck) {

変更後

SoftSPI::SoftSPI(uint8_t mosi, uint8_t miso, uint8_t sck) : SPIClass(SPI_PORT) {
  • ローカルのインクルードファイルを指定

変更前

#include ><SoftwareSPI.h>

変更前

#include "SoftwareSPI.h"

シンプルキューライブラリ

以下のファイルを作成して内容をコピペしてください。

TakSanQueue.h

#ifndef TakSanQueue_h #define TakSanQueue_h template <class DataType, int _Size = 1024> class TakSanQueue { public: TakSanQueue(void) { Clear(); } void Clear(void) { _PutPos = 0; _GetPos = 0; memset(_DataBuffer, 0, sizeof(_DataBuffer)); } bool Put(DataType* data) { unsigned int NextPos = (_PutPos < _Size) ? _PutPos + 1 : 0; if (NextPos == _GetPos) return (false); _DataBuffer[_PutPos] = *data; _PutPos = NextPos; return (true); } bool Get(DataType* data) { unsigned int NextPos; if (_GetPos == _PutPos) return (false); NextPos = (_GetPos < _Size) ? _GetPos + 1 : 0; *data = _DataBuffer[_GetPos]; _GetPos = NextPos; return (true); } bool DataExists(void) { if (_GetPos == _PutPos) return (false); return (true); } private: unsigned int _PutPos; unsigned int _GetPos; // unsigned int _PutErrorCount = 0; // unsigned int _GetErrorCount = 0; // unsigned int _GetCount = 0; DataType _DataBuffer[_Size]; }; #endif //TakSanQueue_h

作成したメインプログラム

以下のファイルの内容をコピペしてソースファイルを作ってください。
但し、タッチパネルの調整値はハードコーディングしてあります。

タッチパネルは個体差が出るはずなので調整値を書き込む必要があります。ソースの下にある、調整方法の手順を参考に書き込んでください。

  • MultiEffector.ino (本体)

MultiEffector.ino

// -----=====<<<<<[[[[[ Include ]]]]]>>>>>=====----- #include <FrontEnd.h> #include <OutputMixer.h> #include <MemoryUtil.h> #include <arch/board/board.h> #include "LGFX_SPRESENSE.hpp" #include <pthread.h> #include <SPI.h> #include "SoftSPI.h" #include "TakSanQueue.h" // -----=====<<<<<[[[[[ Define ]]]]]>>>>>=====----- #define SAMPLE_BUF_SIZE (1024) #define FRAME_BUF_SIZE (SAMPLE_BUF_SIZE * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO) #define ARM_MATH_CM4 #define __FPU_PRESENT 1U #include <cmsis/arm_math.h> #define TAPS 255 #define VOLUME_STEP (256) // [ Control ] #define CONTROL_DIR_UP 1 #define CONTROL_DIR_DOWN 2 // [ For Screen ] #define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 320 // [ For Graphic ] #define CTRL_PANEL_FRAME_SIZE 4 #define CTRL_PANEL_FRAME_LEFT 0 #define CTRL_PANEL_FRAME_TOP 0 #define CTRL_PANEL_FRAME_WIDTH SCREEN_WIDTH #define CTRL_PANEL_FRAME_HEIGHT 64 #define CTRL_PANEL_BASE_WIDTH ( SCREEN_WIDTH - ( CTRL_PANEL_FRAME_SIZE * 2 ) ) #define CTRL_PANEL_BASE_HEIGHT ( CTRL_PANEL_FRAME_HEIGHT - ( CTRL_PANEL_FRAME_SIZE * 2 ) ) #define CTRL_PANEL_BASE_LEFT (CTRL_PANEL_FRAME_LEFT + CTRL_PANEL_FRAME_SIZE ) #define CTRL_PANEL_BASE_TOP (CTRL_PANEL_FRAME_TOP + CTRL_PANEL_FRAME_SIZE ) #define CTRL_PANEL_BASE_COLOR FUNC_PANEL_BASE_COLOR #define CTRL_PANEL_FONT_COLOR CTRL_PANEL_SCL_COLOR #define CTRL_PANEL_FONT_SIZE 2 #define CTRL_PANEL_IND_RADIUS 2 #define CTRL_PANEL_IND_POS_L 10.0f #define CTRL_PANEL_IND_COLOR TFT_BLACK #define CTRL_PANEL_SCL_INNER_L 15.0f #define CTRL_PANEL_SCL_OUTER_L 20.0f #define CTRL_PANEL_SCL_COLOR TFT_WHITE #define CTRL_PAMEL_VOL_RADIUS 15 #define FUNC_PANEL_BASE_WIDTH SCREEN_WIDTH #define FUNC_PANEL_BASE_HEIGHT 240 #define FUNC_PANEL_BASE_LEFT 0 #define FUNC_PANEL_BASE_TOP CTRL_PANEL_FRAME_HEIGHT #define FUNC_PANEL_NUM_ROW 3 #define FUNC_PANEL_NUM_LINE 3 #define FUNC_PANEL_WIDTH (FUNC_PANEL_BASE_WIDTH / FUNC_PANEL_NUM_ROW) #define FUNC_PANEL_HEIGHT (FUNC_PANEL_BASE_HEIGHT / FUNC_PANEL_NUM_LINE) #define FUNC_PANEL_SELIND_SIZE 4 #define FUNC_PANEL_SELIND_COLOR TFT_GREEN #define FUNC_PANEL_FONT_SIZE 1 #define FUNC_PANEL_FONT_COLOR TFT_BLACK #define FUNC_PANEL_BASE_COLOR TFT_NAVY #define FUNC_PANEL_INC_WIDTH (CTRL_PAMEL_VOL_RADIUS*3) #define FUNC_PANEL_INC_HEIGHT (CTRL_PAMEL_VOL_RADIUS) #define TITLE_BAR_BASE_LEFT 0 #define TITLE_BAR_BASE_TOP (SCREEN_HEIGHT - TITLE_BAR_BASE_HEIGHT) #define TITLE_BAR_BASE_WIDTH SCREEN_WIDTH #define TITLE_BAR_BASE_HEIGHT 16 #define TITLE_BAR_BASE_COLOR TFT_DARKGRAY #define TITLE_BAR_TEXT_LEFT (TITLE_BAR_BASE_WIDTH >> 1) #define TITLE_BAR_TEXT_TOP (TITLE_BAR_BASE_TOP + 1) #define TITLE_BAR_FONT_COLOR TFT_BLACK #define TITLE_BAR_FONT_SIZE 2 // [ For Touch Screen ] // pin assign #define PIN_TOUCH_MISO 4 #define PIN_TOUCH_MOSI 5 #define PIN_TOUCH_CLK 7 #define PIN_TOUCH_CS 6 #define PIN_TOUCH_IRQ 2 //#define ALIGN_TOUCH_MODE #define SPI_TOUCH touch_spi #define REG_TOUCH_RDX 0xD0u #define REG_TOUCH_RDY 0x90u #define REG_TOUCH_RDZ1 0xB8u #define REG_TOUCH_RDZ2 0xC8u #define AVERAGE_TOUCH_BUF_SIZE 4 #define INVALID_TOUCH_VAL 8191 #define TOUCH_MIN_X 313 #define TOUCH_MAX_X 3719 #define TOUCH_MIN_Y 358 #define TOUCH_MAX_Y 3833 #define ALIVE_LED_COUNT 10 // [ UART ] #define UART_QUEUE_BUFFER_SIZE 0x100 #define BLE_SERIAL Serial //Serial2 // (Serail for BLE) ToDo: change it Serial because the BLE1705 is NOT working. // [ Function MACROs ] #define ARRAY_SIZEOF(arr) (sizeof(arr) / sizeof(arr[0]) ) // -----=====<<<<<[[[[[ Types ]]]]]>>>>>=====----- typedef enum _func_enum_t_ { func_enum_none = -1, func_enum_min = 0, func_enum_through = func_enum_min, func_enum_equalizer, func_enum_distotion, func_enum_echo_effect, func_enum_reverb_effect, func_enum_voice_changer, func_enum_count } func_enum_t; typedef struct _control_data_t_ { struct { const char* title; const char* command; int16_t* val; int16_t init, min, max, scale; } params; struct { uint16_t x, y; } graphic; } control_data_t; typedef struct _effector_data_t_ { struct { const char* title; const char* command; int32_t sample_size; int32_t frame_size; } params; struct { const char* short_title; uint8_t pos_x, pos_y; uint16_t color; } graphic; const control_data_t* control[5]; struct { void (*before_enter_function)(void); void (*after_leave_function)(void); void (*frontend_signal_input)(AsPcmDataParam pcm, uint8_t* input, uint32_t frame_size); void (*signal_process)(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void (*mixer_stereo_output)(uint8_t* stereo_output, uint32_t frame_size); } func; } effector_data_t; typedef enum _area_enum_t_ { area_enum_none = -1, area_enum_min = 0, area_enum_control = area_enum_min, area_enum_panel, area_enum_title, area_enum_count } area_enum_t; typedef struct _touch_pos_inf_ { area_enum_t type; union { struct { uint8_t pos; uint8_t dir; } control; func_enum_t panel_func; } params; } touch_pos_inf; typedef char serial_queue_data_t; // -----=====<<<<<[[[[[ Valiable ]]]]]>>>>>=====----- // [PThread Items] pthread_t ui_thread; pthread_t uart_thread; pthread_mutex_t mutex_for_signal; // [Digital effector Items] arm_rfft_fast_instance_f32 fft; arm_fir_instance_f32 fir; static func_enum_t current_func = func_enum_none; struct _control_params_ { struct _voice_changer_ { int16_t pitch_shift; } voice_changer; struct _equalizer_ { int16_t bass; int16_t mid; int16_t treble; } equalizer; struct _distotion_ { int16_t gain; int16_t level; } distotion; struct _dummy_ { int16_t dmy1; int16_t dmy2; } dummy; } control_params; FrontEnd *theFrontEnd; OutputMixer *theMixer; float pCoeffs[TAPS]; float pState[TAPS+SAMPLE_BUF_SIZE-1]; const int32_t channel_num = AS_CHANNEL_MONO; const int32_t bit_length = AS_BITLENGTH_16; int32_t sample_size = SAMPLE_BUF_SIZE; int32_t frame_size = sample_size * (bit_length / 8) * channel_num; bool isErr = false; bool signalActive = false; // [For Display] static LGFX lcd; // [For Touch Panel] SoftSPI touch_spi(PIN_TOUCH_MOSI, PIN_TOUCH_MISO, PIN_TOUCH_CLK); // [For UART] TakSanQueue<serial_queue_data_t,UART_QUEUE_BUFFER_SIZE> UartQueue; // -----=====<<<<<[[[[[ Prototype ]]]]]>>>>>=====----- void frontend_attention_cb(const ErrorAttentionParam *param); void mixer_attention_cb(const ErrorAttentionParam *param); static bool frontend_done_cb(AsMicFrontendEvent ev, uint32_t result, uint32_t detail); static void outputmixer_done_cb(MsgQueId requester_dtq, MsgType reply_of, AsOutputMixDoneParam* done_param); static void outputmixer0_send_cb(int32_t identifier, bool is_end); static void frontend_pcm_cb(AsPcmDataParam pcm); void before_enter_function_default(void); void frontend_signal_input_default(AsPcmDataParam pcm, uint8_t* input, uint32_t frame_size); void signal_process_default(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void mixer_stereo_output_default(uint8_t* stereo_output, uint32_t frame_size); void after_leave_function_default(void); void setupFirBPF(int high, int low); void signal_process_through(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void signal_process_equalizer(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void signal_process_voice_changer(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void signal_process_echo_effect(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void signal_process_reverb_effect(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void signal_process_distotion(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void frontend_signal_input(AsPcmDataParam pcm, uint8_t* input, uint32_t frame_size); void signal_process(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size); void mixer_stereo_output(uint8_t* stereo_output, uint32_t frame_size); void change_function(func_enum_t new_func); void next_function(); void draw_screen(); void draw_selected_control(func_enum_t func, uint8_t control, bool update_only); void draw_selected_function(func_enum_t func); bool read_touch_screen(int &x,int &y); touch_pos_inf get_touch_pos_info(int x, int y); void execute_command(char command_str[]); void setup(); void loop(); void ui_thread_function(void *arg); void uart_thread_function(void *arg); // -----=====<<<<<[[[[[ Tables ]]]]]>>>>>=====----- const control_data_t control_dummy_params[2] { { {// title, command val, init min max step "TONE", "@PRM,DMY1=%d", &control_params.dummy.dmy1, 50, 0, 100, 5 }, { CTRL_PANEL_BASE_WIDTH / 3, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y }, { {// title, command val, init min max step "LEVEL", "@PRM,DMY2=%d", &control_params.dummy.dmy2, 50, 0, 100, 5 }, { CTRL_PANEL_BASE_WIDTH * 2 / 3, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y } }; const control_data_t control_equalizer_params[3] { { {// title, command val, init min max step "BASS", "@PRM,BASS=%d", &control_params.equalizer.bass, 50, 0, 100, 5 }, { CTRL_PANEL_BASE_WIDTH / 5, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y }, { {// title, command val, init min max step "MID", "@PRM,MID=%d", &control_params.equalizer.mid, 50, 0, 100, 5 }, { CTRL_PANEL_BASE_WIDTH / 2, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y }, { {// title, command val, init min max step "TREBLE", "@PRM,TREBLE=%d", &control_params.equalizer.treble, 50, 0, 100, 5 }, { CTRL_PANEL_BASE_WIDTH *4 / 5, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y } }; const control_data_t control_distotion_params[2] { { {// title, command val, init min max step "GAIN", "@PRM,GAIN=%d", &control_params.distotion.gain, 100, 10, 500, 20 }, { CTRL_PANEL_BASE_WIDTH / 5, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y }, { {// title, command val, init min max step "LEVEL", "@PRM,LEVEL=%d", &control_params.distotion.level, 32700, 30700, 32700, 100 }, { CTRL_PANEL_BASE_WIDTH *4 / 5, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y } }; const control_data_t control_voice_changer_pitch_shift { {// title, command val, init min max step "PITCH", "@PRM,PITCH=%d", &control_params.voice_changer.pitch_shift, 50, 0, 99, 5 }, { CTRL_PANEL_BASE_WIDTH / 2, (CTRL_PANEL_BASE_HEIGHT / 2) - CTRL_PANEL_FRAME_SIZE} // x, y }; const effector_data_t effector_table[func_enum_count] { { // [ func_enum_through ] { "Through", // Title "@FUNC=THRU", // Command 720, // Sample size 720 * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO // Frame Size }, { "Thrgh", // Short name 0, 0, // Panel draw position (pos_x, pos_y) TFT_SKYBLUE // DrawColor }, { nullptr, nullptr, nullptr, nullptr, nullptr // Control data[5] }, { before_enter_function_default, // before_enter_function after_leave_function_default, // after_leave_function frontend_signal_input_default, // frontend_signal_input signal_process_default, // signal_process mixer_stereo_output_default, // mixer_stereo_output } }, { // [ func_enum_equalizer ] { "Equalizer", // Title "@FUNC=EQLZ", // Command 1024, // Sample size 1024 * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO // Frame Size }, { "Equal", // Short name 1, 0, // Panel draw position (pos_x, pos_y) TFT_SILVER // DrawColor }, { &control_equalizer_params[0], &control_equalizer_params[1], &control_equalizer_params[2], nullptr, nullptr // Control data[5] }, { before_enter_function_default, // before_enter_function after_leave_function_default, // after_leave_function frontend_signal_input_default, // frontend_signal_input signal_process_equalizer, // signal_process mixer_stereo_output_default, // mixer_stereo_output } }, { // [ func_enum_distotion ] { "Distotin", // Title "@FUNC=DIST", // Command 720, // Sample size 720 * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO // Frame Size }, { "Disto", // Short name 2, 0, // Panel draw position (pos_x, pos_y) TFT_RED // DrawColor }, { &control_distotion_params[0], &control_distotion_params[1], nullptr, nullptr, nullptr // Control data[5] }, { before_enter_function_default, // before_enter_function after_leave_function_default, // after_leave_function frontend_signal_input_default, // frontend_signal_input signal_process_distotion, // signal_process mixer_stereo_output_default, // mixer_stereo_output } }, { // [ func_enum_echo_effect ] { "Echo", // Title "@FUNC=ECHO", // Command 720, // Sample size 720 * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO // Frame Size }, { "Echo", // Short name 0, 1, // Panel draw position (pos_x, pos_y) TFT_YELLOW // DrawColor }, { &control_dummy_params[0], &control_dummy_params[1], nullptr, nullptr, nullptr // Control data[5] }, { before_enter_function_default, // before_enter_function after_leave_function_default, // after_leave_function frontend_signal_input_default, // frontend_signal_input signal_process_echo_effect, // signal_process mixer_stereo_output_default, // mixer_stereo_output } }, { // [ func_enum_reverb_effect ] { "Reverb", // Title "@FUNC=REVB", // Command 720, // Sample size 720 * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO // Frame Size }, { "Revrb", // Short name 1, 1, // Panel draw position (pos_x, pos_y) TFT_ORANGE // DrawColor }, { &control_dummy_params[0], &control_dummy_params[1], nullptr, nullptr, nullptr // Control data[5] }, { before_enter_function_default, // before_enter_function after_leave_function_default, // after_leave_function frontend_signal_input_default, // frontend_signal_input signal_process_reverb_effect, // signal_process mixer_stereo_output_default, // mixer_stereo_output } }, { // [ func_enum_voice_changer ] { "Voice Changer", // Title "@FUNC=VOCH", // Command 1024, // Sample size 1024 * (AS_BITLENGTH_16 / 8) * AS_CHANNEL_MONO // Frame Size }, { "Vo.ch", // Short name 2, 1, // Panel draw position (pos_x, pos_y) TFT_PINK // DrawColor }, { &control_voice_changer_pitch_shift, nullptr, nullptr, nullptr, nullptr // Control data[5] }, { before_enter_function_default, // before_enter_function after_leave_function_default, // after_leave_function frontend_signal_input_default, // frontend_signal_input signal_process_voice_changer, // signal_process mixer_stereo_output_default, // mixer_stereo_output } }, }; // -----=====<<<<<[[[[[ Functions ]]]]]>>>>>=====----- void frontend_attention_cb(const ErrorAttentionParam *param) { Serial.println("ERROR: Attention! Something happened at FrontEnd"); if (param->error_code >= AS_ATTENTION_CODE_WARNING) isErr = true; } void mixer_attention_cb(const ErrorAttentionParam *param){ Serial.println("ERROR: Attention! Something happened at Mixer"); if (param->error_code >= AS_ATTENTION_CODE_WARNING) isErr = true; } static bool frontend_done_cb(AsMicFrontendEvent ev, uint32_t result, uint32_t detail){ UNUSED(ev); UNUSED(result); UNUSED(detail); return true; } static void outputmixer_done_cb(MsgQueId requester_dtq, MsgType reply_of, AsOutputMixDoneParam* done_param) { UNUSED(requester_dtq); UNUSED(reply_of); UNUSED(done_param); return; } static void outputmixer0_send_cb(int32_t identifier, bool is_end) { UNUSED(identifier); UNUSED(is_end); return; } static void frontend_pcm_cb(AsPcmDataParam pcm) { static uint8_t mono_input[FRAME_BUF_SIZE]; static uint8_t stereo_output[FRAME_BUF_SIZE*2]; static const bool time_measurement = false; if (time_measurement) { static uint32_t last_time = 0; uint32_t current_time = micros(); uint32_t duration = current_time - last_time; last_time = current_time; Serial.println("duration = " + String(duration)); } frontend_signal_input(pcm, mono_input, frame_size); signal_process((int16_t*)mono_input, (int16_t*)stereo_output, sample_size); mixer_stereo_output(stereo_output, frame_size); return; } void before_enter_function_default(void) { } void frontend_signal_input_default(AsPcmDataParam pcm, uint8_t* input, uint32_t frame_size) { /* clean up the input buffer */ memset(input, 0, frame_size); if (!pcm.is_valid) { Serial.println("WARNING: Invalid data! @frontend_signal_input"); return; } if (pcm.size > frame_size) { Serial.print("WARNING: Captured size is too big! -"); Serial.print(String(pcm.size)); Serial.println("- @frontend_signal_input"); pcm.size = frame_size; } /* copy the signal to signal_input buffer */ if (pcm.size != 0) { memcpy(input, pcm.mh.getPa(), pcm.size); } else { Serial.println("WARNING: Captured size is zero! @frontend_signal_input"); } } void signal_process_default(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); //***/ static float pTmp[SAMPLE_BUF_SIZE]; static float p1[SAMPLE_BUF_SIZE]; q15_t* q15_mono = (q15_t*)mono_input; arm_q15_to_float(&q15_mono[0], &pTmp[0], sample_size); arm_rfft_fast_f32(&fft, &pTmp[0], &p1[0], 0); // TODO arm_rfft_fast_f32(&fft, &p1[0], &pTmp[0], 1); arm_float_to_q15(&pTmp[0], &q15_mono[0], sample_size); mono_input = (int16_t*)q15_mono; //***/ /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n]; } uint32_t duration = micros() - start_time; //@ Serial.println("process time = " + String(duration)); return; } void mixer_stereo_output_default(uint8_t* stereo_output, uint32_t frame_size) { /* Alloc MemHandle */ AsPcmDataParam pcm_param; if (pcm_param.mh.allocSeg(S0_REND_PCM_BUF_POOL, frame_size) != ERR_OK) { Serial.println("ERROR: Cannot allocate memory @mixer_stereo_output"); isErr = false; return; } /* Set PCM parameters */ pcm_param.is_end = false; pcm_param.identifier = OutputMixer0; pcm_param.callback = 0; pcm_param.bit_length = bit_length; pcm_param.size = frame_size*2; pcm_param.sample = frame_size; pcm_param.is_valid = true; memcpy(pcm_param.mh.getPa(), stereo_output, pcm_param.size); int err = theMixer->sendData(OutputMixer0, outputmixer0_send_cb, pcm_param); if (err != OUTPUTMIXER_ECODE_OK) { Serial.println("ERROR: sendData -" + String(err) + "- @mixer_stereo_output"); isErr = true; } } void after_leave_function_default(void) { } //***/ void setupFirBPF(int high, int low) { static int high_ = 0; static int low_ = VOLUME_STEP-1; if ((high_ == high) && (low_ == low)) return; high_ = high; low_ = low; const uint32_t freq_step = AS_SAMPLINGRATE_48000/2/VOLUME_STEP; uint32_t CUTTOFF_LOW_FREQ_HZ = low_*freq_step; uint32_t CUTTOFF_HIGH_FREQ_HZ = high_*freq_step; float Fl = (float)CUTTOFF_LOW_FREQ_HZ/AS_SAMPLINGRATE_48000; float Fh = (float)CUTTOFF_HIGH_FREQ_HZ/AS_SAMPLINGRATE_48000; const int H_TAPS = TAPS/2; int n = 0; for (int k = H_TAPS; k >= -H_TAPS; --k) { if (k == 0) pCoeffs[n] = 2.*(Fh - Fl); else { pCoeffs[n] = 2.*Fh*arm_sin_f32(2.*PI*Fh*k)/(2.*PI*Fh*k) - 2.*Fl*arm_sin_f32(2.*PI*Fl*k)/(2.*PI*Fl*k); } ++n; } for (int m = 0; m < TAPS; ++m) { pCoeffs[m] = (0.5 - 0.5*arm_cos_f32(2*PI*m/TAPS))*pCoeffs[m]; } arm_fir_init_f32(&fir, TAPS, pCoeffs, pState, sample_size); Serial.println("BPF Changed : " + String(CUTTOFF_LOW_FREQ_HZ) + " - " + String(CUTTOFF_HIGH_FREQ_HZ)); } //***/ void signal_process_through(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n]; } uint32_t duration = micros() - start_time; //@ Serial.println("process time = " + String(duration)); return; } void signal_process_equalizer(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); //***/ static float pTmp[SAMPLE_BUF_SIZE]; static float p1[SAMPLE_BUF_SIZE]; static float p2[SAMPLE_BUF_SIZE]; q15_t* q15_mono = (q15_t*)mono_input; int m; float ratio; arm_q15_to_float(&q15_mono[0], &pTmp[0], sample_size); arm_rfft_fast_f32(&fft, &pTmp[0], &p1[0], 0); ratio = 2.0f * (float)control_params.equalizer.bass / 99.0f; for (m = 0; (m < sample_size/3); m++) { p2[m] = p1[m]*ratio; } ratio = 2.0f * (float)control_params.equalizer.mid / 99.0f; for (; m < (sample_size *2/3); m++) { p2[m] = p1[m]*ratio; } ratio = 2.0f * (float)control_params.equalizer.treble / 99.0f; for (; m < sample_size; m++) { p2[m] = p1[m]*ratio; } arm_rfft_fast_f32(&fft, &p2[0], &pTmp[0], 1); arm_float_to_q15(&pTmp[0], &q15_mono[0], sample_size); mono_input = (int16_t*)q15_mono; //***/ /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n]; } uint32_t duration = micros() - start_time; //@ Serial.println("process time = " + String(duration)); return; } void signal_process_voice_changer(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); //***/ static float pTmp[SAMPLE_BUF_SIZE]; static float p1[SAMPLE_BUF_SIZE]; static float p2[SAMPLE_BUF_SIZE]; q15_t* q15_mono = (q15_t*)mono_input; arm_q15_to_float(&q15_mono[0], &pTmp[0], sample_size); arm_rfft_fast_f32(&fft, &pTmp[0], &p1[0], 0); // fft, bitReverse memset(&p2[0], 0, sample_size*sizeof(float)); memcpy(&p2[control_params.voice_changer.pitch_shift*2], &p1[0], (sample_size/2-control_params.voice_changer.pitch_shift*2)*sizeof(float)); arm_rfft_fast_f32(&fft, &p2[0], &pTmp[0], 1); // ifft, bitReverse arm_float_to_q15(&pTmp[0], &q15_mono[0], sample_size); mono_input = (int16_t*)q15_mono; //***/ /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n]; } uint32_t duration = micros() - start_time; //@ Serial.println("process time = " + String(duration)); return; } void signal_process_echo_effect(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); /* memory pool for 1.5sec (= 720*100*(1/48000)) */ static const int lines = 100; static int16_t src_buf[SAMPLE_BUF_SIZE*lines]; /* 2*720*100=144kBytes */ /* shift the buffer data in src_buf and add the latest data to top of the bufer */ memcpy(&src_buf[0], &src_buf[sample_size], sample_size*sizeof(int16_t)*(lines-1)); memcpy(&src_buf[(lines-1)*sample_size], &mono_input[0], sample_size*sizeof(int16_t)); /* set constatns for echo effect */ static const uint32_t D1_in_ms = 300; /* milli sec */ static const uint32_t D2_in_ms = 600; /* milli sec */ static const uint32_t offset1 = D1_in_ms * 48000 / 1000; static const uint32_t offset2 = D2_in_ms * 48000 / 1000; const int src_buf_end_point = lines*sample_size-1; for (int n = sample_size-1; n >= 0; --n) { /* set h1 = 1/2, h2 = 1/4 to reduce calculation costs */ mono_input[(sample_size-1)-n] = src_buf[src_buf_end_point-n] + src_buf[src_buf_end_point-n-offset1]/2 + src_buf[src_buf_end_point-n-offset2]/4; } /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n]; } uint32_t duration = micros() - start_time; //Serial.println("process time = " + String(duration)); return; } void signal_process_reverb_effect(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); /* memory pool for 1.5sec (= 720*100*(1/48000)) */ static const int lines = 100; static int16_t out_buf[SAMPLE_BUF_SIZE*lines]; /* 2*720*100=144kBytes */ /* set constatns for echo effect */ static const uint32_t D_in_ms = 600; /* milli sec */ static const uint32_t offset = D_in_ms * 48000 / 1000; const int src_buf_end_point = lines*sample_size-1; for (int n = sample_size-1; n >= 0; --n) { /* set alpha = 1/2 to reduce calculation costs */ mono_input[(sample_size-1)-n] = mono_input[(sample_size-1)-n] + out_buf[src_buf_end_point-n-offset]/2; } /* shift the buffer data in src_buf and add the latest data to top of the bufer */ memcpy(&out_buf[0], &out_buf[sample_size], sample_size*sizeof(int16_t)*(lines-1)); memcpy(&out_buf[(lines-1)*sample_size], &mono_input[0], sample_size*sizeof(int16_t)); /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n]; } uint32_t duration = micros() - start_time; //@ Serial.println("process time = " + String(duration)); return; } void signal_process_distotion(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { uint32_t start_time = micros(); /* clean up the output buffer */ memset(stereo_output, 0, sizeof(int16_t)*sample_size*2); float tmp; float gain = (float)control_params.distotion.gain / 10.0f; float limitter = (float)control_params.distotion.level; /* copy the signal to output buffer */ for (int n = sample_size-1; n >= 0; --n) { tmp = (float)mono_input[n] * gain; if ( fabs(tmp) > limitter) tmp = (tmp >= 0.0f)? limitter: -1.0*limitter; stereo_output[n*2] = stereo_output[n*2+1] = tmp; } uint32_t duration = micros() - start_time; //@ Serial.println("process time = " + String(duration)); return; } void before_enter_function(void) { sample_size = (current_func != func_enum_none )? effector_table[current_func].params.sample_size: SAMPLE_BUF_SIZE; frame_size = sample_size * (bit_length / 8) * channel_num; if ((current_func != func_enum_none ) && (effector_table[current_func].func.before_enter_function != nullptr)) effector_table[current_func].func.before_enter_function(); signalActive = true; } void frontend_signal_input(AsPcmDataParam pcm, uint8_t* input, uint32_t frame_size) { if ((current_func != func_enum_none ) && (effector_table[current_func].func.frontend_signal_input != nullptr)) effector_table[current_func].func.frontend_signal_input(pcm, input, frame_size); } void signal_process(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) { if ((current_func != func_enum_none ) && (effector_table[current_func].func.signal_process != nullptr)) effector_table[current_func].func.signal_process(mono_input, stereo_output, sample_size); } void mixer_stereo_output(uint8_t* stereo_output, uint32_t frame_size) { if ((current_func != func_enum_none ) && (effector_table[current_func].func.mixer_stereo_output != nullptr)) effector_table[current_func].func.mixer_stereo_output(stereo_output, frame_size); } void after_leave_function(void) { signalActive = false; if ((current_func != func_enum_none ) && (effector_table[current_func].func.after_leave_function != nullptr)) effector_table[current_func].func.after_leave_function(); } void change_function(func_enum_t new_func) { if (current_func != func_enum_none) { Serial.println("Leaving function func = " + String(current_func)); if (effector_table[current_func].func.after_leave_function != nullptr) effector_table[current_func].func.after_leave_function(); //usleep(500 * 1000); } if (new_func != func_enum_none) { Serial.println("Entering function func = " + String(new_func)); current_func = new_func; if (effector_table[current_func].func.before_enter_function != nullptr) effector_table[current_func].func.before_enter_function(); //usleep(500 * 1000); } } void next_function() { func_enum_t new_func = ( current_func >= (func_enum_count -1) )? func_enum_min: current_func + 1; change_function(new_func); } void draw_screen() { int pos_x, pos_y; lcd.fillRect( CTRL_PANEL_FRAME_LEFT, CTRL_PANEL_FRAME_TOP, CTRL_PANEL_FRAME_WIDTH, CTRL_PANEL_FRAME_HEIGHT, CTRL_PANEL_BASE_COLOR); lcd.fillRect(FUNC_PANEL_BASE_LEFT, FUNC_PANEL_BASE_TOP, FUNC_PANEL_BASE_WIDTH, FUNC_PANEL_BASE_HEIGHT, FUNC_PANEL_BASE_COLOR); for (int i = 0; i < ARRAY_SIZEOF(effector_table); i++) { pos_x = effector_table[i].graphic.pos_x; pos_y = effector_table[i].graphic.pos_y; lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ) + FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ) + FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_WIDTH - ( FUNC_PANEL_SELIND_SIZE * 2), FUNC_PANEL_HEIGHT - ( FUNC_PANEL_SELIND_SIZE * 2 ), effector_table[i].graphic.color); lcd.setTextSize(TITLE_BAR_FONT_SIZE); lcd.setTextDatum( textdatum_t::bottom_center); lcd.setTextColor(FUNC_PANEL_FONT_COLOR, effector_table[i].graphic.color); lcd.drawString(effector_table[i].graphic.short_title, FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ) + (FUNC_PANEL_WIDTH >> 1), FUNC_PANEL_BASE_TOP + ( ( pos_y + 1 ) * FUNC_PANEL_HEIGHT ) - 4 ); } /* #define FUNC_PANEL_BASE_LEFT 0 #define FUNC_PANEL_BASE_TOP CTRL_PANEL_FRAME_HEIGHT #define FUNC_PANEL_NUM_ROW 3 #define FUNC_PANEL_NUM_LINE 3 #define FUNC_PANEL_WIDTH (FUNC_PANEL_BASE_WIDTH / FUNC_PANEL_NUM_ROW) #define FUNC_PANEL_HEIGHT (FUNC_PANEL_BASE_HEIGHT / FUNC_PANEL_NUM_LINE) lcd.fillRect(TITLE_BAR_BASE_LEFT, TITLE_BAR_BASE_TOP, TITLE_BAR_BASE_WIDTH, TITLE_BAR_BASE_HEIGHT, effector_table[func].graphic.color); lcd.setTextSize(TITLE_BAR_FONT_SIZE); lcd.setTextDatum( textdatum_t::top_center); lcd.setTextColor(TITLE_BAR_FONT_COLOR, effector_table[func].graphic.color); lcd.drawString(effector_table[func].params.title, TITLE_BAR_TEXT_LEFT, TITLE_BAR_TEXT_TOP); */ delay(1000); } void draw_selected_control(func_enum_t func, uint8_t control, bool update_only) { int x1, y1, x2, y2; int curr; // Range check if (func == func_enum_none ) return; // NULL check if (effector_table[func].control[control] == nullptr) return; // Draw body lcd.fillCircle(effector_table[func].control[control]->graphic.x, effector_table[func].control[control]->graphic.y, CTRL_PAMEL_VOL_RADIUS, effector_table[func].graphic.color); // Only at the first time if ( update_only == false) { // Draw scales for (int i = 30; i <= 330; i += 30 ) { x1 = (int) (CTRL_PANEL_SCL_INNER_L * sin(PI * (float)i / 180.0f)) + effector_table[func].control[control]->graphic.x; y1 = (int) (CTRL_PANEL_SCL_INNER_L * cos(PI * (float)i / 180.0f)) + effector_table[func].control[control]->graphic.y; x2 = (int) (CTRL_PANEL_SCL_OUTER_L * sin(PI * (float)i / 180.0f)) + effector_table[func].control[control]->graphic.x; y2 = (int) (CTRL_PANEL_SCL_OUTER_L * cos(PI * (float)i / 180.0f)) + effector_table[func].control[control]->graphic.y; lcd.drawLine(x1, y1, x2, y2, CTRL_PANEL_SCL_COLOR); } lcd.setTextSize(CTRL_PANEL_FONT_SIZE); lcd.setTextDatum( textdatum_t::top_center); lcd.setTextColor(CTRL_PANEL_FONT_COLOR, CTRL_PANEL_BASE_COLOR); lcd.drawString(effector_table[func].control[control]->params.title, effector_table[func].control[control]->graphic.x, effector_table[func].control[control]->graphic.y + 2+ CTRL_PAMEL_VOL_RADIUS); } // Draw current value indicator curr = map( *effector_table[func].control[control]->params.val, effector_table[func].control[control]->params.min, effector_table[func].control[control]->params.max, 330, 30); x1 = (int) (CTRL_PANEL_IND_POS_L * sin(PI * (float)curr / 180.0f)) + effector_table[func].control[control]->graphic.x; y1 = (int) (CTRL_PANEL_IND_POS_L * cos(PI * (float)curr / 180.0f)) + effector_table[func].control[control]->graphic.y; lcd.fillCircle(x1, y1, CTRL_PANEL_IND_RADIUS, CTRL_PANEL_IND_COLOR); } void draw_selected_function(func_enum_t func) { static func_enum_t last_draw_func = func_enum_none; int pos_x, pos_y; // Range check if (func == func_enum_none ) return; if (last_draw_func != func_enum_none) { pos_x = effector_table[last_draw_func].graphic.pos_x; pos_y = effector_table[last_draw_func].graphic.pos_y; // Draw func panels Selection lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ), FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ), FUNC_PANEL_WIDTH, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_BASE_COLOR); lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ), FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ) + FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_HEIGHT - (FUNC_PANEL_SELIND_SIZE*2), FUNC_PANEL_BASE_COLOR); lcd.fillRect( FUNC_PANEL_BASE_LEFT + ((pos_x + 1 ) * FUNC_PANEL_WIDTH ) - FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ) + FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_HEIGHT - (FUNC_PANEL_SELIND_SIZE*2), FUNC_PANEL_BASE_COLOR); lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ), FUNC_PANEL_BASE_TOP + ( ( pos_y + 1 ) * FUNC_PANEL_HEIGHT ) - FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_WIDTH, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_BASE_COLOR); } last_draw_func = func; pos_x = effector_table[func].graphic.pos_x; pos_y = effector_table[func].graphic.pos_y; // Draw title bar lcd.fillRect(TITLE_BAR_BASE_LEFT, TITLE_BAR_BASE_TOP, TITLE_BAR_BASE_WIDTH, TITLE_BAR_BASE_HEIGHT, effector_table[func].graphic.color); lcd.setTextSize(TITLE_BAR_FONT_SIZE); lcd.setTextDatum( textdatum_t::top_center); lcd.setTextColor(TITLE_BAR_FONT_COLOR, effector_table[func].graphic.color); lcd.drawString(effector_table[func].params.title, TITLE_BAR_TEXT_LEFT, TITLE_BAR_TEXT_TOP); // Draw func panels Selection lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ), FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ), FUNC_PANEL_WIDTH, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_SELIND_COLOR); lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ), FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ) + FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_HEIGHT - (FUNC_PANEL_SELIND_SIZE*2), FUNC_PANEL_SELIND_COLOR); lcd.fillRect( FUNC_PANEL_BASE_LEFT + ((pos_x + 1 ) * FUNC_PANEL_WIDTH ) - FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_BASE_TOP + ( pos_y * FUNC_PANEL_HEIGHT ) + FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_HEIGHT - (FUNC_PANEL_SELIND_SIZE*2), FUNC_PANEL_SELIND_COLOR); lcd.fillRect( FUNC_PANEL_BASE_LEFT + (pos_x * FUNC_PANEL_WIDTH ), FUNC_PANEL_BASE_TOP + ( ( pos_y + 1 ) * FUNC_PANEL_HEIGHT ) - FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_WIDTH, FUNC_PANEL_SELIND_SIZE, FUNC_PANEL_SELIND_COLOR); // Draw control Panel lcd.fillRect(CTRL_PANEL_FRAME_LEFT, CTRL_PANEL_FRAME_TOP, CTRL_PANEL_FRAME_WIDTH, CTRL_PANEL_FRAME_HEIGHT, effector_table[func].graphic.color); lcd.fillRect(CTRL_PANEL_BASE_LEFT, CTRL_PANEL_BASE_TOP, CTRL_PANEL_BASE_WIDTH, CTRL_PANEL_BASE_HEIGHT, CTRL_PANEL_BASE_COLOR); for (int i = 0; i < ARRAY_SIZEOF(effector_table[func].control); i++) { if (effector_table[func].control[i] != nullptr) { draw_selected_control(func, i, false); } } } touch_pos_inf get_touch_pos_info(int x, int y) { touch_pos_inf info; info.type = area_enum_none; if ( y < FUNC_PANEL_BASE_TOP) { int center_x, center_y; for (int i = 0; i < ARRAY_SIZEOF(effector_table[current_func].control); i++) { if (effector_table[current_func].control[i] != nullptr) { center_x = effector_table[current_func].control[i]->graphic.x; center_y = effector_table[current_func].control[i]->graphic.y; if ( ( (center_x - FUNC_PANEL_INC_WIDTH) < x ) && ( x < (center_x + FUNC_PANEL_INC_WIDTH) ) && ( (center_y - FUNC_PANEL_INC_HEIGHT) < y ) && ( y < (center_y + FUNC_PANEL_INC_HEIGHT) ) ) { info.type = area_enum_control; info.params.control.pos = i; info.params.control.dir = (x > center_x)? CONTROL_DIR_UP: CONTROL_DIR_DOWN; } } } } else if ( y < TITLE_BAR_BASE_TOP) { uint8_t pos_x, pos_y; pos_x = ( ( x - FUNC_PANEL_BASE_LEFT ) / FUNC_PANEL_WIDTH); pos_y = ( ( y - FUNC_PANEL_BASE_TOP ) / FUNC_PANEL_WIDTH); for (int i = 0; i < ARRAY_SIZEOF(effector_table); i++) { if ( ( pos_x == effector_table[i].graphic.pos_x ) && ( pos_y == effector_table[i].graphic.pos_y ) ) { info.type = area_enum_panel; info.params.panel_func = i; break; } } } else if ( y < SCREEN_HEIGHT) { info.type = area_enum_title; } return info; } bool read_touch_screen(int &x,int &y) { boolean touched = false; uint8_t z1,z2 ; digitalWrite(PIN_TOUCH_CS, LOW); SPI_TOUCH.transfer(REG_TOUCH_RDX); x = SPI_TOUCH.transfer16(0); x >>= 3 ; SPI_TOUCH.transfer(REG_TOUCH_RDY); y = SPI_TOUCH.transfer16(0); y >>= 3 ; SPI_TOUCH.transfer(REG_TOUCH_RDZ1); z1 = SPI_TOUCH.transfer(0); SPI_TOUCH.transfer(REG_TOUCH_RDZ2); z2 = SPI_TOUCH.transfer(0); digitalWrite(PIN_TOUCH_CS, HIGH); if (z1 > 0 && z2 > 0) { touched = true ; } return touched ; } void execute_command(char command_str[]) { int func; int control; bool command_found = false; int command_len = strlen(command_str); int len; for (func = 0; func < ARRAY_SIZEOF(effector_table); func++) { len = strlen(effector_table[func].params.command); if ( ( command_len >= len ) && ( strncmp(effector_table[func].params.command, command_str, len) == 0) ) { command_found = true; break; } } if (command_found == true) { pthread_mutex_lock(&mutex_for_signal); change_function(func); pthread_mutex_unlock(&mutex_for_signal); Serial.printf("Panel[%d] selected!", func); draw_selected_function(current_func); } else { for (control = 0; control < ARRAY_SIZEOF(effector_table[current_func].control); control++) { if (effector_table[current_func].control[control] != nullptr) { len = strlen(effector_table[current_func].control[control]->params.command); if ( ( command_len >= len-2 ) && ( strncmp(effector_table[current_func].control[control]->params.command, command_str, len-2) == 0) ) { command_found = true; break; } } } if (command_found == true) { int prm = *effector_table[current_func].control[control]->params.val; sscanf(command_str, effector_table[current_func].control[control]->params.command, &prm); pthread_mutex_lock(&mutex_for_signal); *effector_table[current_func].control[control]->params.val = prm; pthread_mutex_unlock(&mutex_for_signal); draw_selected_control(current_func, control, true); } } } void setup() { Serial.begin(115200); // Initialize control valiables for (int j = 0; j < ARRAY_SIZEOF(effector_table); j++) { for (int i = 0; i < ARRAY_SIZEOF(effector_table[j].control); i++) { if (effector_table[j].control[i] != nullptr) { *effector_table[j].control[i]->params.val = effector_table[j].control[i]->params.init; } } } //***/ arm_rfft_fast_init_f32(&fft, SAMPLE_BUF_SIZE); //***/ /* Initialize memory pools and message libs */ initMemoryPools(); createStaticPools(MEM_LAYOUT_RECORDINGPLAYER); /* setup FrontEnd and Mixer */ theFrontEnd = FrontEnd::getInstance(); theMixer = OutputMixer::getInstance(); /* set clock mode */ theFrontEnd->setCapturingClkMode(FRONTEND_CAPCLK_NORMAL); /* begin FrontEnd and OuputMixer */ theFrontEnd->begin(frontend_attention_cb); theMixer->begin(); Serial.println("Setup: FrontEnd and OutputMixer began"); /* activate FrontEnd and Mixer */ theFrontEnd->setMicGain(0); theFrontEnd->activate(frontend_done_cb); theMixer->create(mixer_attention_cb); theMixer->activate(OutputMixer0, outputmixer_done_cb); delay(100); /* waiting for Mic startup */ Serial.println("Setup: FrontEnd and OutputMixer activated"); /* Initialize FrontEnd */ AsDataDest dst; dst.cb = frontend_pcm_cb; theFrontEnd->init(channel_num, bit_length, SAMPLE_BUF_SIZE, AsDataPathCallback, dst); Serial.println("Setup: FrontEnd initialized"); /* Set rendering volume */ theMixer->setVolume(-10, -10, -10); /* -10dB */ /* Unmute */ board_external_amp_mute_control(false); theFrontEnd->start(); Serial.println("Setup: FrontEnd started"); /* Prepare Mutex */ if (pthread_mutex_init(&mutex_for_signal, NULL) != 0) { printf("Error; pthread_mutex_init.\n"); exit(1); } /* Starting threads*/ pthread_create(&ui_thread, NULL, ui_thread_function, NULL); pthread_create(&uart_thread, NULL, uart_thread_function, NULL); /* Select Function and Draw Title */ change_function(func_enum_through); draw_selected_function(current_func); } void loop() { /* Fail when error occores */ if (isErr == true) { signalActive=false; board_external_amp_mute_control(true); theFrontEnd->stop(); theFrontEnd->deactivate(); theMixer->deactivate(OutputMixer0); theFrontEnd->end(); theMixer->end(); Serial.println("Capturing Process Terminated"); while(1) {}; } usleep(500 * 1000); // DO NOT not use "delay(msec)" } void ui_thread_function(void *arg) { int alive_led = LOW; int alive_cntdown = ALIVE_LED_COUNT; static int x_buf[AVERAGE_TOUCH_BUF_SIZE], y_buf[AVERAGE_TOUCH_BUF_SIZE]; int buf_pos = 0; int last_x = INVALID_TOUCH_VAL, last_y = INVALID_TOUCH_VAL; #if defined(ALIGN_TOUCH_MODE) int min_x = 35767, max_x = -35768; int min_y = 35767, max_y = -35768; #endif // defined(ALIGN_TOUCH_MODE) // Initialize pins pinMode(LED2, OUTPUT); digitalWrite(LED2, LOW); pinMode(LED3, OUTPUT); digitalWrite(LED3, LOW); pinMode(PIN_TOUCH_IRQ, INPUT); // Initialize touch screen SPI_TOUCH.begin(); SPI_TOUCH.setClockDivider(SPI_CLOCK_DIV4); SPI_TOUCH.setBitOrder(MSBFIRST); SPI_TOUCH.setDataMode(SPI_MODE0); /* Initialize display */ lcd.init(); lcd.startWrite(); lcd.clear(TFT_BLACK); lcd.setSwapBytes(true); draw_screen(); /* UI Thread main loop */ while(1) { int x, y, x_sum, y_sum; int touch_pos_x, touch_pos_y; // Alive LED indicator proc. if (alive_cntdown > 0) alive_cntdown--; else { alive_cntdown = ALIVE_LED_COUNT; alive_led = (alive_led == LOW)? HIGH: LOW; digitalWrite(LED3, alive_led); } // Read the touch position. if (read_touch_screen(x, y) == true) { Serial.printf("Touch(%d,%d)", x, y); // Sum. for the average; x_buf[buf_pos] = x; y_buf[buf_pos] = y; buf_pos++; buf_pos %= AVERAGE_TOUCH_BUF_SIZE; x_sum = 0; y_sum = 0; for (int i = 0; i<AVERAGE_TOUCH_BUF_SIZE; i++) { x_sum += x_buf[i]; y_sum += y_buf[i]; } x = x_sum / AVERAGE_TOUCH_BUF_SIZE; y = y_sum / AVERAGE_TOUCH_BUF_SIZE; #if defined(ALIGN_TOUCH_MODE) if (max_x < x && x != INVALID_TOUCH_VAL) max_x = x; if (max_y < y && y != INVALID_TOUCH_VAL) max_y = y; if (min_x > x) min_x = x; if (min_y > y) min_y = y; Serial.printf(" Ave(%d,%d), x:%d~%d, y:%d,%d ", x, y, min_x, max_x, min_y, max_y); #endif // defined(ALIGN_TOUCH_MODE) touch_pos_x = map(x, TOUCH_MIN_X, TOUCH_MAX_X, (SCREEN_WIDTH - 1), 0); touch_pos_y = map(y, TOUCH_MIN_Y, TOUCH_MAX_Y, 0, (SCREEN_HEIGHT - 1)); Serial.printf(" Pos(%d, %d)\n", touch_pos_x, touch_pos_y); last_x = touch_pos_x; last_y = touch_pos_y; } else { if ( ( last_x != INVALID_TOUCH_VAL ) && ( last_y != INVALID_TOUCH_VAL ) ) { touch_pos_inf info = get_touch_pos_info(last_x, last_y); if ( info.type == area_enum_panel ) { digitalWrite(LED2, HIGH); pthread_mutex_lock(&mutex_for_signal); change_function(info.params.panel_func); pthread_mutex_unlock(&mutex_for_signal); Serial.printf("Panel[%d] selected!", info.params.panel_func); draw_selected_function(current_func); digitalWrite(LED2, LOW); } else if (info.type == area_enum_control) { digitalWrite(LED2, HIGH); pthread_mutex_lock(&mutex_for_signal); if (info.params.control.dir == CONTROL_DIR_UP) { *effector_table[current_func].control[info.params.control.pos]->params.val += effector_table[current_func].control[info.params.control.pos]->params.scale; if (*effector_table[current_func].control[info.params.control.pos]->params.val > effector_table[current_func].control[info.params.control.pos]->params.max) { *effector_table[current_func].control[info.params.control.pos]->params.val = effector_table[current_func].control[info.params.control.pos]->params.max; } Serial.printf("Control[%d][%d]->params.val +%d => %d!", current_func, info.params.control.pos, effector_table[current_func].control[info.params.control.pos]->params.scale, *effector_table[current_func].control[info.params.control.pos]->params.val); } else if (info.params.control.dir == CONTROL_DIR_DOWN) { *effector_table[current_func].control[info.params.control.pos]->params.val -= effector_table[current_func].control[info.params.control.pos]->params.scale; if (*effector_table[current_func].control[info.params.control.pos]->params.val < effector_table[current_func].control[info.params.control.pos]->params.min) { *effector_table[current_func].control[info.params.control.pos]->params.val = effector_table[current_func].control[info.params.control.pos]->params.min; } Serial.printf("Control[%d][%d] -%d => %d!", current_func, info.params.control.pos, effector_table[current_func].control[info.params.control.pos]->params.scale, *effector_table[current_func].control[info.params.control.pos]->params.val); } else ; // Nothing to do pthread_mutex_unlock(&mutex_for_signal); draw_selected_control(current_func, info.params.control.pos, true); digitalWrite(LED2, LOW); } else ; //nothng to do } last_x = INVALID_TOUCH_VAL; last_y = INVALID_TOUCH_VAL; } usleep(50 * 1000); // DO NOT not use "delay(msec)" } // Thread は止めない物とする。 } void uart_thread_function(void *arg) { // int alive_led = LOW; int alive_cntdown = ALIVE_LED_COUNT; int bufpos = 0; serial_queue_data_t code; static char command_str[128]; // Initialzie pinMode(LED1, OUTPUT); digitalWrite(LED1, LOW); // Thread main loop while(1) { // Alive LED indicator proc. if (alive_cntdown > 0) alive_cntdown--; else { alive_cntdown = ALIVE_LED_COUNT; alive_led = (alive_led == LOW)? HIGH: LOW; digitalWrite(LED1, alive_led); } while (BLE_SERIAL.available() > 0) { code = (serial_queue_data_t)BLE_SERIAL.read(); UartQueue.Put(&code); } usleep(100 * 1000); // DO NOT not use "delay(msec)" while ( true == UartQueue.Get(&code) ) { switch(code) { case '\r': command_str[bufpos] = '\0'; break; case '\n': command_str[bufpos] = '\0'; if ( 0 != strlen(command_str)) { execute_command(command_str); } bufpos = 0; break; default: command_str[bufpos] = code; if (bufpos >= sizeof(command_str)-1) { Serial.printf("Command Buffer Overflow @UART(%d)", bufpos); } else bufpos++; break; } } usleep(100 * 1000); // DO NOT not use "delay(msec)" } } }

タッチパネル調整手順

先ずは、プログラムをタッチパネル調整モードで書き込む必要があります。

MultiEffector.ino のソースファイルの 89行目付近にある

MultiEffector.ino

//#define ALIGN_TOUCH_MODE

の行頭の "//" を消して有効行にして書き込んでください。

起動したら、 ArduinoIDE の [Tool] - [シリアルモニター] を選んでシリアルモニターを開きます。

Touch(XXX, XXX) Ave(XXX,XXX), x:###~###, y:###~,###

という内容がログに出てきます('XXX' の部分には座標の数値が、 '###' の部分が調整値です。

X の範囲(最小値と最大値)と Y の範囲(最小値と最大値)が出てきます。
画面の隅を触ると最大値最小値が更新されます。これ以上更新されなくなるまで、画面の隅を触ってください。更新されなくなったらそれが調整値です。

ソースの 100行目付近にある

MultiEffector.ino

#define TOUCH_MIN_X 313 #define TOUCH_MAX_X 3719 #define TOUCH_MIN_Y 358 #define TOUCH_MAX_Y 3833

の部分にそれぞれの値を記入してください。
その後 MultiEffector.ino のソースファイルの 89行目付近のコードは 頭に '//' を付けると調整値のログは出なくなりますので、その状態で再度プログラムを書き込んでください。
ちゃんとタッチできるようになったはずです。

最後に

今回も圧倒的に時間が足りなくて(…というより変なハマり方して時間を浪費してしまったかな)まだまだ消化不良です。
せっかく提供いただいたデバイスで使えなかったデバイスもありました。

以下の通り、まだまだ実装したい機能もまだまだあります。

  • エフェクタの数を増やしたい。
  • エフェクターの内部の処理を作りこみたい。
  • 色々パラメータを調整出来る様にしたい。
  • パラメータの設定がやりにくいので改善したい。
  • ギターチューナーも組み込みたい。
  • エフェクター選択画面にアイコンを付けてもっとかっこよくしたい。
  • 各エフェクターアイコンを押すと、 ON/OFF という動作にして、同時に複数のエフェクタを選択できるようにしたい。
  • BLEを使ったフットペダルやスマホ等の外部機器からの制御が出来る様にしたい。

また、記事を見て真似をしてもらうにはまだまだ情報も足りないし、記事も見直しが必要かと思います。コンテストの審査中は一旦編集不可になるので、それが終わった後に、しっかり記事の整備しようと思っています。

1
TakSan0のアイコン画像
大阪在住の本業組込み系SEです。ハードに近いところのソフト屋さんです。 電子工作は趣味でやっています。 よろしくお願いします。 ▼こちらにも別の作品を投稿しています。参考まで。 https://protopedia.net/prototyper/taksan
  • TakSan0 さんが 2024/01/31 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する