FM シンセサイザー用 GUI を作る
以前に、DX7 FM シンセサイザー用オープンソースをポーティングしました。
当初は、スタンダード MIDI ファイルの演奏を考えていましたが、パースするのが意外と大変そうなので、ストールしていました。
その後、MIDI 演奏は、とりあえず後回しにして、鍵盤を作り、音色を変更する GUI を実装しました。
当初、鍵盤の GUI も、widget_director クラスで管理できるものと思いましたが、鍵盤の場合、同時押しなど、通常の GUI とは異なるケアを行う必要があります、現在の widget クラスを改修するのは、かなり複雑になると考え、専用のクラスを実装しました。
また、タッチパネル(FT5206)サービスクラスでは、同時二点押しまでしかサービスしていませんでしたが、FT5206 の最大数4点までのサービスに修正しました。
※和音を出すのに3点以上が必要です。
DX7 の音色ファイルのロードと切り替え
FM シンセサイザーのソースには、音色は1つだけしか設定されていません。
実機は最大32色まで変更が可能なバンクがあります。
音色ファイルは、「DX7_0628.SYX」というもので、ネットで見つけました。
※たったの4Kバイト程度です。
このアプリでは、SD カードのルートに「DX7_0628.SYX」に置いておくと、起動して3秒くらいしてからロードします。
※SDカードのマウントに遅延がある為
使える音色は以下のものです:
0: 'PIANO 1 '
1: 'FM PIANO A'
2: 'PIANO 1 '
3: 'HARD ROADS'
4: 'PIANOBELL2'
5: 'T 23 '
6: 'RHODES-CHO'
7: 'PIPES A'
8: 'PIPES 2 '
9: 'ROADSFLUTE'
10: 'ROADFLUTE2'
11: 'PLUCKEDRUM'
12: 'LO/HI STR2'
13: 'OBEHIND '
14: 'ANLGBRASS '
15: 'FATSYNTH A'
16: 'JL PONTY 1'
17: 'Strings #2'
18: 'Orchestra '
19: 'PLUCKIN' '
20: 'OOH AHH EE'
21: 'T 22 '
22: 'BassFlute2'
23: 'BassFlute3'
24: 'TIGHTPIANO'
25: 'BASS FLUTE'
26: 'PAUL STRGS'
27: 'BASS/PIA.1'
28: '2 OR MORE '
29: 'TIMPANI 2'
30: 'MALE CHOIR'
31: 'F.CHORUS 2'
シンセサイザークラスに音色をセットするのは、内部的には、MIDI データとして食わせるようです。
※ロードしたデータをそのままMIDIのストリームに食わせるだけです。
bool read_synth_color_(const char* filename) noexcept
{
utils::file_io fin;
if(fin.open(filename, "rb")) {
uint8_t tmp[4096 + 8];
if(fin.read(tmp, sizeof(tmp)) == sizeof(tmp)) {
ring_buffer_.Write(tmp, sizeof(tmp));
{ // データを処理させる為、エンジンを動かす。
const uint32_t len = SYNTH_SAMPLE_RATE / 60;
int16_t tmp[len];
synth_unit_.GetSamples(len, tmp);
}
for(int i = 0; i < 32; ++i) {
char tmp[12];
synth_unit_.get_patch_name(i, tmp, 12 - 1);
tmp[11] = 0;
utils::sformat(" %2d: %s", &synth_color_name_[i * 16], 16) % (i + 1) % tmp;
}
}
return true;
} else {
return false;
}
}
マルチタッチと発音
RX72N Envision Kit のタッチパネルは、静電容量タイプで、4点までのマルチタッチが可能なタイプです。
音の強弱は難しいとしても、キーボード(ピアノ)のようなインターフェースには向いています。
void service_note_() noexcept
{
for(int i = 0; i < 21; ++i) {
const auto& key = synth_gui_.get_keyboard().get(static_cast<SYNTH_GUI::KEYBOARD::key>(i));
if(key.positive_) {
uint8_t tmp[3];
tmp[0] = 0x90;
tmp[1] = 0x3C + i;
tmp[2] = 0x7f;
ring_buffer_.Write(tmp, 3);
}
if(key.negative_) {
uint8_t tmp[3];
tmp[0] = 0x80;
tmp[1] = 0x3C + i;
tmp[2] = 0x7f;
ring_buffer_.Write(tmp, 3);
}
}
}
GUI キーボードで得た、押した状態、離した状態で、それぞれ、MIDI データを作成して、食わせると音が鳴ります。
押している間、音が連続して鳴ります。
また、マルチタッチでは「和音」が鳴らせる事が大きいです。
流石に、このサイズ(1.5オクターブ程)では、簡単な曲しか演奏できませんが、ガジェットとして楽しいものです。
GUI を作成する要点
widget_director クラスでは、現在は、widget の配置ツールなどは無く、プログラムコードによって widget の配置などを行う必要があります。
例えば、電卓のような物なら、グリッド状にボタンが配置されているので、配置ツールが無くても簡単です。
今回は、音色を変更する為、二つのスイッチと、音色名を表示するボックスを作成しました。
このような場合、座標は、比較的簡単な計算で求められるので、定数を定義して、計算式で widget のリソースを生成しています。
とりあえず、ルールとして:
- 中央の上部に配置する
- 左右に音色を変更するボタンを配置する
- 中央に音色名を表示する
- オクターブボタンは、画面の端に表示する
- オクターブ領域表示は、中央に表示する
static const int16_t SC_NAME_LEN = 16; ///< With EOT
static const int16_t SC_NUM = 32; ///< 音色最大数
static const int16_t OCT_NUM = 5; ///< オクターブ域
...
static const int16_t SC_LOC = 10; ///< ボタン関係、縦の位置
static const int16_t SC_SPC = 10; ///< ボタンとの隙間
static const int16_t CENTER = 480/2; ///< X 中心
static const int16_t SC_BTN_SZ = 30; ///< ボタンサイズ
static const int16_t SC_TEX_W = 8 * SC_NAME_LEN; ///< テキスト横幅
static const int16_t SC_TEX_H = 24; ///< テキスト高さ
static const int16_t OCT_LOC = 40; ///< オクターブ関係、縦の位置
static const int16_t OCT_AREA_W = 300;
static const int16_t OCT_AREA_H = 30;
static const int16_t OCT_BTN_SZ = 50; ///< ボタンサイズ
...
typedef gui::widget WIDGET;
typedef gui::button BUTTON;
typedef gui::text TEXT;
typedef gui::slider SLIDER;
BUTTON sc_idx_m_;
TEXT sc_name_;
BUTTON sc_idx_p_;
BUTTON octave_m_;
SLIDER octave_d_;
BUTTON octave_p_;
...
synth_gui(RENDER& render, TOUCH& touch) noexcept :
render_(render), touch_(touch), widd_(render, touch),
keyboard_(render, touch),
sc_idx_m_(vtx::srect(CENTER-SC_TEX_W/2-SC_BTN_SZ-SC_SPC, SC_LOC, SC_BTN_SZ, SC_BTN_SZ), "<"),
sc_name_ (vtx::srect(CENTER-SC_TEX_W/2, SC_LOC+(SC_BTN_SZ-SC_TEX_H)/2, SC_TEX_W, SC_TEX_H), ""),
sc_idx_p_(vtx::srect(CENTER+SC_TEX_W/2+SC_SPC, SC_LOC, SC_BTN_SZ, SC_BTN_SZ), ">"),
octave_m_(vtx::srect(0, OCT_LOC, OCT_BTN_SZ, OCT_BTN_SZ), "<<"),
octave_d_(vtx::srect(CENTER-OCT_AREA_W/2, OCT_LOC+(OCT_BTN_SZ-OCT_AREA_H)/2, OCT_AREA_W, OCT_AREA_H)),
octave_p_(vtx::srect(480-OCT_BTN_SZ, OCT_LOC, OCT_BTN_SZ, OCT_BTN_SZ), ">>"),
sc_idx_(0), sc_idx_before_(0), sc_name_org_(nullptr),
oct_idx_(2)
{ }
BUTTON、TEXT、SLIDER クラスは、コンストラクターで座標を計算して設定しています。
このように定数を設けて、それを起点に座標を生成すると、簡単なルールで、座標の設定を自動化出来て便利です。
ボタンの大きさや隙間など、パラメーターとしているので、簡単に変更出来ます。
計算で行うと、計算式が正しければ、理にかなった正しい表示が行えます。
C++17 では、より複雑なルールや条件分岐など、プログラム的な物を、constexpr を使って定義する事も出来ます。
※ constexpr については、ググッて下さい。
constexpr はコンパイル時に計算されるので、どんなに複雑でも、実機の処理負荷には影響を与えません。
まとめ
マルチタッチを有効に活用するアプリとして、「鍵盤」は、最適なアプリと思えます。
今後、MIDI ファイルの演奏など、アプリを充実させていきたいと思います。
追記
Arduino 環境用に、スタンダード MIDI のプレイヤー(パーサー)があったので、ポーティングしてみました。
多少、改造しましたが、想定の範囲で演奏出来るようです。
ソースコードは、コミットしてあります。
スタンダード MIDI ファイルはネットにあるものが大体使えるようですが、演奏出来ないファイルもあり、その点は調査中です。
「@」ボタンを押すと、ファイラーが開くので、MIDIファイルを選択すれば演奏が始まります。
演奏中、音色を変更する事も出来ます。