手袋楽器GloveToneのスケッチとシールドを公開!

お待たせしました!手袋楽器 GloveTone のスケッチとシールドを公開します。
GloveToneはArduino unoを音源とし、手袋に仕込んだ曲げセンサーで音程と音量をコントロールする楽器(のようなもの)です。
まずはGloveToneのデモを御覧ください。

いろいろやってる割に「さいたさいた」レベルかい…ってツッコミは、まあ甘んじてお受けしましょう。
以下、シールド(Arduinoと組み合わせる基板)とスケッチ(ソースコード)を公開します。

IO Board シールド

まずは入出力の回路を載せたシールド。一般的にはFritzing で配線図を公開する方が多いようですが、不慣れなので今回は回路図で描きますが、リクエストがあれば追って配線図での提供も検討します。ちなみに回路図は水晶堂さんのBsch3Vで描きました。
IO Board
IO Board posted by (C)よしむら
回路は極めて単純で、入力側は5Vを10kΩの抵抗R1、R2と曲げセンサーで分圧してアナログ入力ピンに入力。0.1μFのコンデンサC1、C2はノイズ除去用ですね。出力側はデジタル出力を1kΩの抵抗R3、R4で分圧してレベルを下げ、これと0.1μFのコンデンサC3の組み合わせでローパスフィルターを形成、直流成分カットの10μFの電解コンデンサC4を通してミニジャックJ1に出力しています。音量、音質を問わなければ圧電スピーカーを直結しても良いかもしれません。あと、モード切り替え用にトグルスイッチSW1を付けてあります。マイコンの入力にプルアップ抵抗が内蔵されているのでシンプルにスイッチのみでOK。
これらの回路をプロトタイプ・シールドに組み上げてあります。今回は面倒でケースに入れなかったため、ミニジャックもスイッチも基板直付け。スイッチのピンが太くて穴に入らないので無理矢理キリで広げ、半田だけの強度では不安だったので接着剤で固定しています。
IO Board外観
IO Board外観 posted by (C)よしむら
IO Board裏面
IO Board裏面 posted by (C)よしむら
曲げセンサーは4.5″のもの2.2″のものがあり、当初4.5″を2本使っていたのですが、音量調整用が断線してしまい、そちらのみ2.2″のものに取り替えています。抵抗値が違うので、分圧用の抵抗を変えたほうが分解能を有効に使えるのですが、今回はそのままになっています。

GloveTone スケッチ

続いてスケッチ。まずは方針として音をどう作るか…本当は単純な1、0切り替えの矩形波の方が余計なノイズがなくて音はきれいなんですが、それじゃあ単調でつまんない、ということで、関数で波形を合成して PWM で出力することにしました。しかも、2つの微妙に周波数をずらした波形を作り、両者を重ねることでちょっと厚みを出しています。
波形としてはサイン波はテーブルで持ち、ノコギリ波、三角波、矩形波は関数で生成しています。今回のデモではノコギリ波と矩形波を使用しました。波形データの生成はPWMのタイマー割り込み毎に実行し、loop内ではセンサーの値の読み込みを行なっています。
波形のピッチを連続変化させるモードと全音階に限定するモードを備え、モードスイッチで切り替えています(今回使用していませんが、半音階モードも備えてあります)。なお、PWMの諸設定、音階を限定するアイディアについてはAuduinoをかなり参考にさせていただきました。
以下のコードは無保証で自由に使っていただいて構いません。

// glove_tone2.ino
// GloveTone - Tone instrument using flex sensors
// 2012-12-15(Sat) yoshimura.shunji@gmail.com
#include 
#include 
#define MODE_PIN    2      // Mode switch -> Digital I/O 2
#define PWM_PIN     3      // PWM out -> Digital I/O 3
#define PWM_VALUE   OCR2B
#define PWM_INTERRUPT  TIMER2_OVF_vect
#define     PSENS_PIN   0      // Pitch sensor -> Analog in 0
#define     VSENS_PIN   1      // Volume sensor -> Analog in 1
#define     POFFSET         600    // Offset value for flex sensor 4.5"
#define     VOFFSET     750    // Offset value for flex sensor 2.2"
#define     VSCALE      128
// Sine waveform table
const byte sineTable[] =
{128, 130, 131, 133, 134, 136, 137, 139, 141, 142, 144, 145, 147, 148,
150, 151, 153, 155, 156, 158, 159, 161, 162, 164, 165, 167, 168, 170,
171, 173, 174, 176, 177, 178, 180, 181, 183, 184, 186, 187, 188, 190,
191, 192, 194, 195, 196, 198, 199, 200, 202, 203, 204, 206, 207, 208,
209, 210, 212, 213, 214, 215, 216, 217, 219, 220, 221, 222, 223, 224,
225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 234, 235, 236, 237,
238, 239, 239, 240, 241, 242, 242, 243, 244, 244, 245, 246, 246, 247,
247, 248, 249, 249, 250, 250, 250, 251, 251, 252, 252, 253, 253, 253,
254, 254, 254, 254, 255, 255, 255, 255, 255, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 255, 255, 255, 255,
255, 254, 254, 254, 254, 253, 253, 253, 252, 252, 251, 251, 250, 250,
250, 249, 249, 248, 247, 247, 246, 246, 245, 244, 244, 243, 242, 242,
241, 240, 239, 239, 238, 237, 236, 235, 234, 234, 233, 232, 231, 230,
229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 217, 216, 215,
214, 213, 212, 210, 209, 208, 207, 206, 204, 203, 202, 200, 199, 198,
196, 195, 194, 192, 191, 190, 188, 187, 186, 184, 183, 181, 180, 178,
177, 176, 174, 173, 171, 170, 168, 167, 165, 164, 162, 161, 159, 158,
156, 155, 153, 151, 150, 148, 147, 145, 144, 142, 141, 139, 137, 136,
134, 133, 131, 130, 128, 126, 125, 123, 122, 120, 119, 117, 115, 114,
112, 111, 109, 108, 106, 105, 103, 101, 100, 98, 97, 95, 94, 92, 91,
89, 88, 86, 85, 83, 82, 80, 79, 78, 76, 75, 73, 72, 70, 69, 68, 66,
65, 64, 62, 61, 60, 58, 57, 56, 54, 53, 52, 50, 49, 48, 47, 46, 44,
43, 42, 41, 40, 39, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,
25, 24, 23, 22, 22, 21, 20, 19, 18, 17, 17, 16, 15, 14, 14, 13, 12,
12, 11, 10, 10, 9, 9, 8, 7, 7, 6, 6, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 2,
2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 9, 9, 10,
10, 11, 12, 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 20, 21, 22, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 40,
41, 42, 43, 44, 46, 47, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61,
62, 64, 65, 66, 68, 69, 70, 72, 73, 75, 76, 78, 79, 80, 82, 83, 85,
86, 88, 89, 91, 92, 94, 95, 97, 98, 100, 101, 103, 105, 106, 108, 109,
111, 112, 114, 115, 117, 119, 120, 122, 123, 125, 126};
const byte cromaticTable[] =
{58, 61, 65, 69, 73, 77, 82, 86, 92, 97, 103, 109, 115, 122, 129,
137, 145, 154, 163, 173, 183, 194, 206, 218, 231, 244,259, 274,
291, 308, 326, 346};
const byte diatonicTable[] =
{58, 58, 65, 69, 69, 77, 77, 86, 92, 92, 103, 103, 115, 115, 129,
137, 137, 154, 154, 173, 183, 183, 206, 206, 231, 231, 259, 274,
274, 308, 308, 346};
int pitchValue;
int volumeValue;
uint16_t phaseAcc1;
uint16_t phaseInc1;
uint16_t phaseAcc2;
uint16_t phaseInc2;
byte waveform;
float gain;
void audioOn() {
TCCR2A = _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(CS20);
TIMSK2 = _BV(TOIE2);
}
int sineWave(int phase) { // phase : 0 - 511
return (int)sineTable[phase];
}
int sawtoothWave(int phase) {
return phase / 2;
}
int triangleWave(int phase) {
return phase > 256 ? 511 - phase : phase;
}
int squareWave(int phase) {
return  phase > 256 ? 255 : 0;
}
void setup() {
int i;
pinMode(MODE_PIN, INPUT_PULLUP);
pinMode(PWM_PIN, OUTPUT);
audioOn();
phaseAcc1 = 0;
phaseAcc2 = 0;
}
void loop() {
pitchValue = analogRead(PSENS_PIN) - POFFSET;
volumeValue = analogRead(VSENS_PIN) - VOFFSET;
if (pitchValue  255) {
pitchValue = 255;
}
}
SIGNAL(PWM_INTERRUPT) {
if (digitalRead(MODE_PIN) == HIGH) {
phaseInc1 = 2 * pitchValue;
} else {
//  phaseInc1 = 2 * cromaticTable[pitchValue / 8];
phaseInc1 = 2 * diatonicTable[pitchValue / 8];
}
phaseInc2 = phaseInc1 + 1;
gain = volumeValue / (float)VSCALE;
if (gain  1.0) {
gain = 1.0;
}
phaseAcc1 = (phaseAcc1 + phaseInc1) % 8192;
phaseAcc2 = (phaseAcc2 + phaseInc2) % 8192;
waveform = (byte)((sawtoothWave(phaseAcc1 / 16) +
squareWave(phaseAcc2 / 16)) / 2);
PWM_VALUE = (int)((float)waveform * gain) ;
}

次回、センサーの手袋への装着に関するノウハウ(っていっても大したもんじゃないですが)、今後の展望などについても書こうと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です