Arduino Touchscreen PWM Sound Output
https://diyelectromusic.com/2021/08/09/arduino-touchscreen-pwm-sound-output/
の #機械翻訳
https://youtu.be/AbL5x0P905Y
ダフネ・オラムに多少インスパイアされて、私がタッチスクリーンで本当にやりたかったのは、波形を描くことでした。これを実現するために必要な部品はすべて揃ったので、このプロジェクトではタッチスクリーンとPWMによる音声出力、そしてスライダー・ポテンショメータから得た「描く」というアイデアを組み合わせています。
Somewhat inspired by Daphne Oram, the thing I really wanted to do with my touchscreen was to use it to draw a waveform. I now have all the bits and pieces I need to do this, so in this project I’m combining the touch screen with PWM sound output and the “drawing” idea from my slider potentiometers.
警告!実験には古い機器や中古機器の使用を強くおすすめします。高価な機器が故障しても当方は一切責任を負いません!
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
本プロジェクトで使う主要な Arduino チュートリアルはこちらです:
These are the key Arduino tutorials for the main concepts used in this project:
Arduino PWM Sound Output – Part 2
Arduino MIDI Slider PWM Waveform Generator
Arduino を初めて使う方は、まず「はじめに Getting Started」ページをご覧ください。
部品リスト
Arduino Uno
ILI9488 タッチスクリーンディスプレイ&シールド
市販の MIDI モジュール Ready-Made MIDI Modules および MIDI ソース
(オプション)Arduino PWM Output Filter Circuit – Part 2
アンプ回路およびスピーカー
回路構成
これは以前作成したモジュールをつなぎ合わせているだけなので、特に新たに「回路図」を示すほどのものはありません。Arduino PWM 出力フィルター回路シールド(Arduino PWM Output Filter Circuit – Part 2)を使用しており、ディスプレイで使用しているピンの都合上、ピン 9 での PWM 出力ができないため、「Arduino PWM Sound Output – Part 2」で紹介されている手法を応用し、ピン 3 を音声出力用に使っています。
The Circuit
This is largely plugging together previous modules, so there is no “circuit” to describe as such. I’ve made use of my Arduino PWM Output Filter Circuit shield and we have to note that due to the pins used by the display I can’t use PWM output on pin 9, so I’ve also used the techniques described in Arduino PWM Sound Output – Part 2 for sound output on pin 3.
また、Arduino の RX ピンには MIDI 入力を接続し、デモ用としてフィルター後の出力端子に安価なオシロスコープも接続しています。
I also need a MIDI input to the Arduino’s RX pin and for demonstration purposes I’ve also hooked up my cheap oscilloscope to the post-filter output.
https://gyazo.com/66ba4726f9935ef03d7b1a5a3658c939
コード概要
このプロジェクトの目的は、タッチスクリーン上で波形を描画し、その波形を PWM 合成のソースとして、再生中の MIDI ノートに応じた周波数で出力できるようにすることです。
多くのコードは前述のプロジェクトから流用しています。私が最も苦労したのは、ピン9ではなくピン3で PWM サウンドを生成する方法を考え出すことでした。そのため、Timer2 の設定方法と使い方を詳細にまとめたブログ記事のフォローアップとして、その手順を組み込みました(詳しくはこちら Arduino PWM Sound Output – Part 2)。
“MIDI 波形生成” の基本処理は「Arduino MIDI Slider PWM Waveform Generator」から取っていますが、マルチプレクサのコードやウェーブテーブル生成時の補間処理は不要になったため、大幅に簡略化しています。代わりに「Arduino Touchscreen MIDI Controller」からグラフィック描画ルーチンを流用し、ディスプレイ上でタッチした点から直接ウェーブテーブルを構築しています。
最初の試作では、ディスプレイ座標とウェーブテーブルインデックスを 1:1 で対応させました。動作自体は問題なかったものの、表示領域の大部分が未使用のまま中央の 256×256 ピクセル領域だけを使う形になってしまいました。
そこで最終的には、ウェーブテーブルをディスプレイ全体に拡大・縮小できる機能を追加しました。ディスプレイの解像度が 320×480 なので、Y 軸はスケール 1、X 軸はスケール 1.5 が理想的です。小数点スケーリングに対応するため、すべての表示パラメータを 12.4 固定小数点(整数部12ビット+小数部4ビット)の 16 ビット値として扱い、実際の座標の 16 倍の値を使うようにしています。この変換を簡単に行うために、2 つのマクロを定義しました。
The Code
The aim of this project is to be able to draw a waveform on the touchscreen and have it played out as the source for some PWM synthesis on the frequency required by the MIDI note being played.
Much of the code for this project is taken from the fore-mentioned previous projects. The main sticking point for me was having to work out how to create PWM sound on pin 3 rather than pin 9, hence the follow-up to my PWM blog post with the details for how to configure and use Timer 2 (full details here).
The basic “MIDI wave generation” is taken from Arduino MIDI Slider PWM Waveform Generator, although it is actually greatly simplified, being able to lose the multiplexer code and the interpolation to create the wavetable. Instead, I’ve taken the graphics routines from Arduino Touchscreen MIDI Controller and build the wavetable directly from the touched points on the display.
My initial trial used a direct 1:1 mapping between display coordinates and the wavetable. This worked fine, but mean most of the display was left unused, there being just a smaller 256×256 square in the centre.
In the end I’ve allowed for the ability to scale the wavetable to the display. But as the display is 320×480, the ideal scaling factors are 1 on the Y-axis and 1.5 on the X-axis. To allow for a fractional scaling, I set all the display parameters using 12.4 fixed point arithmetic. The values are all 16-bit values with 12-bits for the integer component and 4 bits for a fraction. This means all values are 16 times larger than the actual display coordinates they map on to. To support this, I’ve created two macros for easy conversion.
以下に、ディスプレイのスケーリングに用いた関連コードと定数を示します。
Here is the relevant code and values I’ve used to scale the display.
code:scale
#define fp2d(x) ((uint16_t)((x) >> 4)) // 固定小数点12.4を整数座標に戻す(16で除算)
#define d2fp(x) ((uint16_t)((x) << 4)) // 整数座標を固定小数点12.4に変換(16を乗算)
#define WTX_MIN (48*16UL) // ウェーブテーブル描画領域のX最小位置(固定小数点)
#define WTX_SCALE 24UL // X方向スケール(1.5*16)
#define WTX_SIZE (384*16UL) // X方向描画サイズ(固定小数点)
#define WTY_MIN (32*16UL) // ウェーブテーブル描画領域のY最小位置(固定小数点)
#define WTY_SCALE (1*16UL) // Y方向スケール(1*16)
#define WTY_SIZE (256*16UL) // Y方向描画サイズ(固定小数点)
#define BORDER 4 // 波形描画枠の太さ
void displayWaveTable () {
// 全体をクリアして枠を描き、現在のウェーブテーブルに合わせて再描画
gfx->fillRect(fp2d(WTX_MIN)-BORDER,
fp2d(WTY_MIN)-BORDER,
fp2d(WTX_SIZE)+BORDER*2,
fp2d(WTY_SIZE)+BORDER*2,
RED);
gfx->fillRect(fp2d(WTX_MIN),
fp2d(WTY_MIN),
fp2d(WTX_SIZE),
fp2d(WTY_SIZE),
BLACK);
// 全 256 エントリを更新
for (int i = 0; i < WTSIZE; i++) {
updateWaveTable(i);
}
}
void updateWaveTable (int idx) {
// 指定インデックスのウェーブテーブル値を画面上に再描画
int drawval = 255 - wavetableidx;
// まず縦線で前回のドットを消去
gfx->drawFastVLine(fp2d(WTX_MIN + idx*WTX_SCALE),
fp2d(WTY_MIN),
fp2d(WTY_SIZE),
BLACK);
// 新しい位置にピクセルを描画
gfx->drawPixel(fp2d(WTX_MIN + idx*WTX_SCALE),
fp2d(WTY_MIN + drawval*WTY_SCALE),
GREEN);
}
void setWaveTable (int xc, int yc) {
// タッチ座標を固定小数点に変換
xc = d2fp(xc);
yc = d2fp(yc);
// 描画範囲外なら何もしない
if (xc < WTX_MIN || xc >= WTX_MIN + WTX_SIZE ||
yc < WTY_MIN || yc >= WTY_MIN + WTY_SIZE) {
return;
}
// X座標→ウェーブテーブルインデックス、Y座標→波形値に変換
int index = (xc - WTX_MIN) / WTX_SCALE;
int value = 255 - ((yc - WTY_MIN) / WTY_SCALE);
wavetableindex = value;
// そのポイントだけ再描画
updateWaveTable(index);
}
displayWaveTable() 関数は起動時に呼び出され、現在の設定に応じてウェーブテーブル全体をクリアして再描画します。
updateWaveTable() 関数は、ウェーブテーブルの特定のエントリに対応するピクセルだけをクリアして再描画します。そのため、テーブル全体を描画するにはこの関数を 256 回呼び出す必要があります。どちらの関数も、12.4 固定小数点形式から実際のディスプレイ座標へ変換を行っています。
setWaveTable() 関数は、タッチイベント検出時に呼び出すよう設計されています。画面上のタッチ位置(X, Y 座標)を受け取り、描画範囲内かチェックした後、X 座標をウェーブテーブルのインデックスに、Y 座標をウェーブテーブル内の値にスケーリングして変換します。そしてそのインデックスだけ updateWaveTable() を呼んで再描画します。
以前のタッチ操作プロジェクトと同様に、ディスプレイの初期化とスキャンには gfxSetup() と gfxLoop() の 2 つの関数を使います。後者 (gfxLoop()) がタッチを検出した際に setWaveTable() を呼び出す役割を担っています。
PWM と MIDI の処理部分は「Arduino MIDI Slider PWM Waveform Generator」とまったく同じですが、PWM 出力をタイマー2 とピン3 で行うために「Arduino PWM Sound Output – Part 2」のコードを流用しています。
GitHub でもソースコードを公開していますので、そちらからご覧ください。
The displayWaveTable() function is called on start-up and clears and redisplays the entire table according to its current settings.
The updateWaveTable() function just clears and then re-draws the pixels corresponding to one specific entry in the wavetable, so to draw the whole thing this needs to be called 256 times. Both of these need to convert from the 12.4 format back to actual display coordinates.
The setWaveTable function is designed to be called when a touch event is detected. It takes display coordinates corresponding to the touch point, checks them, and converts them into values to update the wavetable. The X-coordinate is scaled and turned into a wavetable index, and the Y-coordinate is scaled and turned into the value within the wavetable. Then updateWaveTable is called to re-draw the display for that specific point.
As with the previous touch projects, there are two functions to initialise and scan the display – gfxSetup() and gfxLoop(). The latter is responsible for calling the setWaveTable () function when a touch is detected.
The PWM and MIDI handling is exactly the same as for the Arduino MIDI Slider PWM Waveform Generator – but I’ve had to use the code from Arduino PWM Sound Output – Part 2 to use Timer 2 and pin 3 as the PWM output.
Find it on GitHub here.
締めくくりの考察
以前からずっと試してみたいと思っていたアイデアです。結果は動画でお聞きいただけます。ひとつ面白いクセがあって、波形を描く際に素早く動かすとピクセルが抜け落ちることがあります。そのため、本来は滑らかな波形にところどころズレた点が散在し、素早く描くと画面上に二重の波形が重なるような効果が得られることもあります。
このおかげで、手描きの波形には独特の「ザラつき」が生まれます。エッジ部分や不規則なテーブルエントリが追加の倍音を生み出し、PWM 出力ではその影響がはっきりと聴き取れます。これにより、生成できる音色は非常に複雑になります。
フィルターなどでさらに加工するオシレーター源として使うのも面白いでしょう。
このアイデアは、1960年代にフィルムに手描きで音を描いていたダフネ・オラムの「オラミクス」に大いに触発されたものです。今では市販の部品を組み合わせるだけで、まさに同じことがデジタルで実現できるのです!
また、これを利用してシンセサイザーの他のパラメータを制御するのも非常に興味深いです。エンベロープを描くのは明白な応用例ですね。画面に一度にいくつ並べられるか試してみたいです。
Closing Thoughts
I’ve been wanting to try this for some time. You can hear the results in the video. There is one quirk, which I actually quite like. When drawing waveforms you get the odd missed pixel, especially if you draw quickly. This means that an otherwise fairly smooth waveform has the odd out-of-step portion dotted around. If you draw really quickly you can almost get two waveforms superimposed on the display!
The upshot of this is that almost any hand-drawn waveform has a real grittiness to it. There are lots of additional harmonics due to edges and odd entries in the wavetable and with the PWM signal this is really audible in the output. This makes the type of sounds you can generate really quite complex.
This would be a great thing to use as an oscillator source for some fancy further processing with filters and so on.
This was mostly inspired by Daphne Oram’s Oramics. She had to draw sounds using paint on film back in the 1960s. Now it is possible to do that digitally with a hand-full of off-the-shelf components!
It would be really interesting to use this to control some of the other parameters of a synthesizer somehow. Drawing envelopes is the obvious one. I wonder how many I could get on the screen at once.
#Kevin
https://gyazo.com/7e58053c1e1104a520957b999c920c66