計算によると明日のはずなので、寒い中ロケハンに
出かけてみた。天気は良かったので、今日は今日で
人出があるだろうと斥候。
お、目つきの悪い人が寄ってきた。
さすがに風をさえぎるものがないので寒いんだけど、
あのくらいの人出ならまぁ、明日天気しだいで
出かけてもイイかなぁ。
GPV予報で明日の天気を調べてみる。
http://weather-gpv.info/
…だめじゃん。あぁ残念。また明日の最新予報で
考え直そう。
さて、昨日のアイデアを形に。
http://ichirowo.com/2011/06/arduino-midi-sound-source/
このサイトのソースを元に、昨日の方針でスケッチを
書き、アレコレと頭を悩ませながら弄っては動かし、
弄っては動かし…。
Arduinoはシミュレータがないのがやっぱり難点。
どこまでがちゃんと動いてて、どこでトラブって
いるのかが判断しかねる…。デバッグ用にLCDは
残しておいたほうがよかったななぁ。MIDI信号が
USARTを使っちゃってるから、変数を眺めたり
全然出来ない…。
でも、アレコレ弄りながらオシロで信号を眺めて
いると、とりあえず信号は出るようになった。
周波数も電圧もそれなりにあってるんだけど、なぜか
圧電スピーカを繋いでも音が出てこない。出てこない
っていうか、極微小のゴソゴソノイズが出ている感じ。
同じ圧電スピーカをtoneで鳴らしてみると…鳴る。
キャリー62500HzのPWMで作ったDDSでは圧電素子を
鳴らすことは出来ないのか?なぜだろう?
仕方ないので、代替案として、1kΩの抵抗と直列に
イヤフォンを繋いで聞いてみる。
…おぉ。鳴ってる。鳴ってるジャン。多少ノイズ
は載ってるものの、正弦波の音だ!
SparkFunのMIDI breakoutシールドでMIDIマスター
キーボードに繋いでみて、和音を鳴らしたり
ベロシティーに変化を付けてみたりする。
…うん。バッチリだな。とりあえず最低限の機能
は盛り込めた感。8オペでも鳴るか試してみる。
…うん。鳴る。ok。
いずれにしても相当スペック的にギリギリアウト
な感じで動かしているので、毎秒20000回の
割り込みでDDS計算しているのに、実際は
そこまで追従してないところはあるだろうな。
音の濁りとかはきっとそこらへんが一番大きな
原因っぽい。実際は10000Hzまでは出ないだろう。
20000回/秒というと、割り込み1回分のクロック
は800ぽっち。出来ることは限られる。
とはいえ、角速度ωの計算はきちんと20000回/秒
で行われているから、音程が狂うってことは
理論上無い。音程は狂わないけど、DDS計算が
部分的に追いついていないので、10000Hzは
出せないだろうな。音程を上げていくと、音色が
徐々にFM変調掛けた様な金属音に近づいていく。
とりあえず動いているスケッチがこれ。参考にした
サイトのスケッチや、以前作ったアセンブラの
プログラムなどを切り貼りして完全にフランケン
状態。読みにくく、汚い。
#include <avr/interrupt.h>
#include <MIDI.h>
const int max_notes = 8;
const int max_velocity = 127;
int d_out = 10; // pwm out
int led = 13;
int ch;
volatile unsigned int tcnt2;
volatile unsigned int op_theta[max_notes]; // angle(theta) for each operator (16bits width)
int op_tone[max_notes]; // each tone read from MIDI
int op_velocity[max_notes]; // each velocity read from MIDI
//int op_chanel[max_notes]; // each chanel of op read from MIDI
int op_bend[max_notes]; // each bend read from MIDI
int op_using[max_notes]; // whether op is in use
char wave_form[] = {
0x00,0x03,0x06,0x09,0x0C,0x10,0x13,0x16,
0x19,0x1C,0x1F,0x22,0x25,0x28,0x2B,0x2E,
0x31,0x33,0x36,0x39,0x3C,0x3F,0x41,0x44,
0x47,0x49,0x4C,0x4E,0x51,0x53,0x55,0x58,
0x5A,0x5C,0x5E,0x60,0x62,0x64,0x66,0x68,
0x6A,0x6B,0x6D,0x6F,0x70,0x71,0x73,0x74,
0x75,0x76,0x78,0x79,0x7A,0x7A,0x7B,0x7C,
0x7D,0x7D,0x7E,0x7E,0x7E,0x7F,0x7F,0x7F,
0x7F,0x7F,0x7F,0x7F,0x7E,0x7E,0x7E,0x7D,
0x7D,0x7C,0x7B,0x7A,0x7A,0x79,0x78,0x76,
0x75,0x74,0x73,0x71,0x70,0x6F,0x6D,0x6B,
0x6A,0x68,0x66,0x64,0x62,0x60,0x5E,0x5C,
0x5A,0x58,0x55,0x53,0x51,0x4E,0x4C,0x49,
0x47,0x44,0x41,0x3F,0x3C,0x39,0x36,0x33,
0x31,0x2E,0x2B,0x28,0x25,0x22,0x1F,0x1C,
0x19,0x16,0x13,0x10,0x0C,0x09,0x06,0x03,
0x00,0xFD,0xFA,0xF7,0xF4,0xF0,0xED,0xEA,
0xE7,0xE4,0xE1,0xDE,0xDB,0xD8,0xD5,0xD2,
0xCF,0xCD,0xCA,0xC7,0xC4,0xC1,0xBF,0xBC,
0xB9,0xB7,0xB4,0xB2,0xAF,0xAD,0xAB,0xA8,
0xA6,0xA4,0xA2,0xA0,0x9E,0x9C,0x9A,0x98,
0x96,0x95,0x93,0x91,0x90,0x8F,0x8D,0x8C,
0x8B,0x8A,0x88,0x87,0x86,0x86,0x85,0x84,
0x83,0x83,0x82,0x82,0x82,0x81,0x81,0x81,
0x81,0x81,0x81,0x81,0x82,0x82,0x82,0x83,
0x83,0x84,0x85,0x86,0x86,0x87,0x88,0x8A,
0x8B,0x8C,0x8D,0x8F,0x90,0x91,0x93,0x95,
0x96,0x98,0x9A,0x9C,0x9E,0xA0,0xA2,0xA4,
0xA6,0xA8,0xAB,0xAD,0xAF,0xB2,0xB4,0xB7,
0xB9,0xBC,0xBF,0xC1,0xC4,0xC7,0xCA,0xCD,
0xCF,0xD2,0xD5,0xD8,0xDB,0xDE,0xE1,0xE4,
0xE7,0xEA,0xED,0xF0,0xF4,0xF7,0xFA,0xFD}; // wave form data declaration
//char wave_form[] = {
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,
// 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81}; // wave form data declaration
unsigned int omega_table[] = {
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
0 ,
10 ,
36 ,
62 ,
88 ,
115 ,
141 ,
167 ,
193 ,
220 ,
246 ,
272 ,
298 ,
324 ,
351 ,
377 ,
403 , //NOTE_B2
429 , //NOTE_C3
455 , //NOTE_CS3
482 , //NOTE_D3
511 , //NOTE_DS3
541 , //NOTE_E3
573 , //NOTE_F3
606 , //NOTE_FS3
642 , //NOTE_G3
682 , //NOTE_GS3
721 , //NOTE_A3
763 , //NOTE_AS3
809 , //NOTE_B3
859 , //NOTE_C4
908 , //NOTE_CS4
963 , //NOTE_D4
1019, //NOTE_DS4
1081, //NOTE_E4
1144, //NOTE_F4
1212, //NOTE_FS4
1285, //NOTE_G4
1360, //NOTE_GS4
1442, //NOTE_A4 (1442 * 20000 / 256 / 256 = 440hz)
1527, //NOTE_AS4
1619, //NOTE_B4
1714, //NOTE_C5
1815, //NOTE_CS5
1923, //NOTE_D5
2038, //NOTE_DS5
2159, //NOTE_E5
2287, //NOTE_F5
2425, //NOTE_FS5
2569, //NOTE_G5
2723, //NOTE_GS5
2884, //NOTE_A5
3054, //NOTE_AS5
3237, //NOTE_B5
3431, //NOTE_C6
3634, //NOTE_CS6
3850, //NOTE_D6
4080, //NOTE_DS6
4322, //NOTE_E6
4578, //NOTE_F6
4850, //NOTE_FS6
5138, //NOTE_G6
5443, //NOTE_GS6
5767, //NOTE_A6
6111, //NOTE_AS6
6475, //NOTE_B6
6858, //NOTE_C7
7265, //NOTE_CS7
7697, //NOTE_D7
8156, //NOTE_DS7
8641, //NOTE_E7
9155, //NOTE_F7
9699, //NOTE_FS7
10276, //NOTE_G7
10886, //NOTE_GS7
11534, //NOTE_A7
12219, //NOTE_AS7
12947, //NOTE_B7
13717, //NOTE_C8
14533, //NOTE_CS8
15398, //NOTE_D8
16312 }; //NOTE_DS8
// 20000sps ,table = 256samples ,range = 256times(integer=8bits,dicimal=8bits)
/* make timer2 interrupt every 20000hz */
void timer2_set() {
float prescaler = 0.0;
TIMSK2 &= ~((1<// prescaler set to 8
TCCR2B &= ~((1<int)((float)F_CPU * 0.00005 / prescaler) - 1;
OCR2A = tcnt2;
}
/* timer2 start and stop */
void timer2_start() {
TCNT2 = 0;
TIMSK2 |= (1<void timer2_stop() {
TIMSK2 &= ~(1</* process at timer2 interruption occurred */
ISR(TIMER2_COMPA_vect) {
int i;
for (i=0;i/* make timer1 set for pwm output (mode=5 : fast pwm 8 bit) */
void timer1_set() {
TCCR1B = 0; // stop timer1
OCR1BH = 0; // (high 8 bits as 0)
OCR1BL = 127; // set output level as middle of 0..255 as initial value
TIMSK1 = 0; // invalid compare match interrapt
// and invalid timer1 overflow
TCCR1A = (1<// use timer1 as mode5
// fast pwm mode, as top value = 0xFF
// OC1B is HIGH when count up compare match
TCCR1B = (1<// non-pre-scaler as clock source
}
/* timer1 : pwm data output on ocr1b */
void timer1_pwm_out(char data1) {
OCR1BH = 0;
OCR1BL = data1; // output data1 for pwm (alalog data)
}
/* calc and output data */
void calc_and_out() {
long data1;
int data2;
int i;
data1 = 0;
for (i=0;inoInterrupts(); // atomic access
data2 = (op_theta[i])>>8;
interrupts();
data1 = data1 + (wave_form[data2] * op_velocity[i]);
}
timer1_pwm_out(data1 / (max_velocity * max_notes) + 127);
}
/* input data from midi */
void midi_in() {
int i;
int data1;
int data2;
if (MIDI.read()) {
switch(MIDI.getType()) {
case NoteOn:
data1 = MIDI.getData1(); // note no
data2 = MIDI.getData2(); // velocity
if(data2 == 0){
for (i=0;iif(op_using[i] == data1){
op_using[i] = 0;
op_tone[i] = 0;
op_velocity[i] = 0;
op_bend[i] = 0;
break;
}
}
break;
}
else {
digitalWrite(led,HIGH);
for (i=0;iif(op_using[i] == 0){
op_using[i] = data1;
op_tone[i] = data1;
op_velocity[i] = data2;
op_bend[i] = 0;
break;
}
}
digitalWrite(led,LOW);
}
break;
case NoteOff:
data1 = MIDI.getData1(); // note no
for (i=0;iif(op_using[i] == data1){
op_using[i] = 0;
op_tone[i] = 0;
op_velocity[i] = 0;
op_bend[i] = 0;
break;
}
}
break;
case PitchBend:
data2 = MIDI.getData2();
for (i=0;i// op_bend[i] = data2 + 19;
}
break;
default:
break;
}
}
}
/* init variables */
void init_val() {
int i;
for (i=0;i//op_chanel[i] = 0;
op_bend[i] = 0;
op_using[i] = 0;
}
}
/*** ***/
/*** main logic ***/
/*** ***/
void setup() {
/* init variables */
init_val();
/* pin setting */
pinMode(d_out, OUTPUT);
pinMode(led, OUTPUT);
/* set up timers */
timer2_set();
//timer2_stop();
timer2_start();
timer1_set();
/* start up MIDI */
MIDI.begin();
Serial.begin(38400); // for conect to PC
// ( comment out if conect direct to MIDI instrument at 31250bps)
ch = 1;
MIDI.setInputChannel(ch); // MIDI channel
MIDI.turnThruOff(); // turn off thru-out
}
void loop() {
/* main loop */
midi_in();
calc_and_out();
}
(なんで1行おきになっちゃうんだろう?)
http://www.arduino.cc/playground/Main/MIDILibrary
MIDIライブラリのV3.2を使用。
チャンネルはとりあえず1固定(内部の変数では0固定)。
デジタル10番に、1kΩの抵抗と直列にしてイヤホンを
繋ぐか、LMM386で増幅掛けてからスピーカに繋ぐと
ちゃんと音が出るんだけど、前述の通り圧電スピーカ
はそのままデジタル10番に繋いでも音がボソボソ、
LM386通しても音がボソボソ。原因わからず。
なんだろうねぇ?
本当は全チャンネルからの入力対応にして、
8ポリのどこかが空いていればマッピングさせる
つもりだったんだけど、ArduinoのMIDIライブラリ
は特定のチャンネルだけフィルタリング掛けて、
他のチャンネルは読み捨ててしまっているらしい。
0から順に15まで読み出そうとループ処理を入れて
みても、MIDI信号の断片しか読み出せない…
うーーーーーん。MIDIライブラリを使う以上は
避けられないみたい。扱いがラクチンなんだけど
マルチチャンネル入力が出来ないのはなぁ…
自分で組みなおさないといかんか…
あと、ピッチベンドの処理が怪しいので、ちょっと
ホイールを回すと、その後音程が狂っちゃったり
色々駄目。この辺のバグは後回し。
そのほか、ピアノや弦楽器のようにサステインだけ
はエンベロープ掛けたいんだけど、あとは処理能力
との兼ね合いかなぁ。とりあえず動いたのでこれは
これでfixしておいて、今後の修正は別スケッチに
しよう。
とりあえずここまで。