SMFを読み込み音の波形データを作るプログラム (C言語)

オリジナル SMFを読み込み音の波形データを作るプログラム (C言語) のHTMLファイルを元にして、 簡易なおれおれマークダウン 2019秋 のマークダウンの形式で書き直し作業中です。

出来るだけオリジナルの見た目を崩さぬように。

2021/JUL/21


目次


はじめに


音を鳴らしてみる

soxコマンドのインストール

$ sudo apt-get install sox
  :
$ which sox
/usr/bin/sox

$ which play
/usr/bin/play

$ sox --version
sox:      SoX v14.4.0

(Ubuntu 12.10)

$ sox --version
sox: SoX v14.0.1

SIN波のデータを作り音を鳴らしてみる

音のPCMデータ形式

サンプリング周波数 8000 Hz
ビット長 8 bit
符号 なし
チャンネル数 1 (モノラル)

鳴らしてみる音

音の周波数 880 Hz
時間 1 秒

プログラム

prog_sin.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int smp_freq = 8000;
	int smp_cnt, iv;
	double freq = 880;
	double len = 1.0;
	double sec, v;

	smp_cnt = 0;
	do{
		sec = (double)smp_cnt / smp_freq;
		v = sin(2* M_PI * freq * sec);
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}while(sec < len);
	return 0;
}

/* EOF */

解説

コンパイル

$ gcc -o prog_sin prog_sin.c -lm

実行

$ ./prog_sin > prog_sin.raw

生成されたデータを少し覗き見すると、こんな感じ...

$ hd prog_sin.raw | head
00000000  80 d0 fc ef ae 59 15 03  2a 79 ca fb f2 b6 61 1a  |.....Y..*y....a.|
00000010  02 24 71 c4 f8 f6 bd 69  1f 01 1f 69 bd f6 f8 c4  |.$q....i...i....|
00000020  71 24 02 1a 61 b6 f2 fb  ca 79 2a 03 15 59 ae ef  |q$..a....y*..Y..|
00000030  fc d0 80 30 04 11 52 a7  eb fd d6 87 36 05 0e 4a  |...0..R.....6..J|
00000040  9f e6 fe dc 8f 3c 08 0a  43 97 e1 ff e1 97 43 0a  |.....<..C.....C.|
00000050  08 3c 8f dc fe e6 9f 4a  0e 05 36 87 d6 fd eb a7  |.<.....J..6.....|
00000060  52 11 04 30 80 d0 fc ef  ae 59 15 03 2a 79 ca fb  |R..0.....Y..*y..|
00000070  f2 b6 61 1a 02 24 71 c4  f8 f6 bd 69 1f 01 1f 69  |..a..$q....i...i|
00000080  bd f6 f8 c4 71 24 02 1a  61 b6 f2 fb ca 79 2a 03  |....q$..a....y*.|
00000090  15 59 ae ef fc d0 80 30  04 11 52 a7 eb fd d6 87  |.Y.....0..R.....|

playコマンドの主なオプション

-t raw : 生のPCMデータタイプ
-r <サンプリング周波数>
-b 8   : ビット長8ビット
-b 16  : ビット長16ビット
-u     : 符号なし
-s     : 符号あり
-c 1   : モノラル (デフォルト)
-c 2   : ステレオ

Sox v14.0.1 の場合

-b : ビット長8ビット
-w : ビット長16ビット

音を鳴らしてみます

$ play -t raw -r 8000 -b 8 -u prog_sin.raw

「ポー」

playコマンドはファイル名に '-' を指定すると、 標準入力からデータ受け取るので、次のコマンドでも同様に動作します

$ cat prog_sin.raw | play -t raw -r 8000 -b 8 -u -

prog_sin コマンドの処理速度が十分速ければ、 データファイルに落とすまでもなく、次のコマンドで実行可能です

$ ./prog_sin | play -t raw -r 8000 -b 8 -u -

WAV形式に落すには、soxコマンドで出力ファイルに拡張子.wavのファイルを指定します

$ ./prog_sin | sox -t raw -r 8000 -b 8 -u - prog_sin.wav

WAV形式のファイルの再生

$ play prog_sin.wav

prog_sin.mp3

和音を鳴らしてみる

例えばメジャーコード A なら、その周波数は

A 880 Hz
C# 880 * pow(2, 4/12) Hz
E 880 * pow(2, 7/12) Hz

プログラム

prog_chord.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int smp_freq = 8000;
	int smp_cnt, iv, i;
	double freqs[3];
	double len = 1.0;
	double sec, v;

	freqs[0] = 880;
	freqs[1] = freqs[0] * pow(2, 4.0 / 12);
	freqs[2] = freqs[0] * pow(2, 7.0 / 12);

	for(i=0; i<3; i++){
		smp_cnt = 0;
		do{
			sec = (double)smp_cnt / smp_freq;
			v = sin(2 * M_PI * freqs[i] * sec);
			iv = 128 + (int)(v * 127);
			putchar(iv);
			smp_cnt++;
		}while(sec < len);
	}

	smp_cnt = 0;
	len = 2;
	do{
		sec = (double)smp_cnt / smp_freq;
		v = 0;
		for(i=0; i<3; i++){
			v += sin(2 * M_PI * freqs[i] * sec);
		}
		v /= 3;
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}while(sec < len);
	return 0;
}

/* EOF */

解説

$ gcc -o prog_chord prog_chord.c -lm

$ ./prog_chord | play -t raw -r 8000 -b 8 -u -

prog_chord.mp3

「ポープーピービョー」


SMF (Standar MIDI File) について

参考URL

SMFの構造概略

分類 種別 データ長 内容
ヘッダ ID 4バイト "MThd" アスキー・コード 'M','T','h','d' の 4バイト
サイズ 4バイト 以降のヘッダのバイト数をビッグエンディアンで格納
ヘッダの場合は値6固定なので、00,00,00,06 の4バイト
フォーマットタイプ 2バイト 値0, 1, 2 のいづれかをビッグエンディアンで格納
ここではフォーマットタイプ 0 の SMF のみ扱うので、
00,00 の2バイト
トラック数 2バイト トラック数をビッグエンディアンで格納
フォーマットタイプが0の場合は、トラック数は1固定なので、
00,01 の2バイト
時間分解能 2バイト 四分音符の分割数をビッグエンディアンで格納
四分音符1つをここで指定した値で分割した長さが、時間の最小単位
ただし1バイト目の最上位ビットが'1'の場合は、別の扱いとなる
ここでは1バイト目の最上位ビットが'0'のSMFのみを扱う
トラック ID 4バイト "MTrk" アスキー・コード 'M','T','r','k' の 4バイト
サイズ 4バイト 以降のトラックのバイト数をビッグエンディアンで格納
デルタタイム 可変長
イベント 可変長
デルタタイム 可変長
イベント 可変長
:
デルタタイム 可変長
イベント 可変長

デルタタイムとは

可変長形式の整数

範囲 データ長 最小値 最大値
0〜127 1バイト 0x00 0x7f
128〜16,383 2バイト 0x81, 0x00 0xff, 0x7f
16,384〜2,097,151 3バイト 0x81, 0x80, 0x00 0xff, 0xff, 0x7f
2,097,152〜268,435,455 4バイト 0x81, 0x80, 0x80, 0x00 0xff, 0xff, 0xff, 0x7f
268435456〜34,359,738,367 5バイト 0x81, 0x80, 0x80, 0x80, 0x00 0xff, 0xff, 0xff, 0xff, 0x7f
:

イベントとは

イベントのサイズは可変長なので、 最上位ビットでイベントの末尾は判断できそうな気もしますが、 実はそうはいきません

イベントの先頭の1バイト目の最上位ビットが'0'だった場合、 前回のイベント同じ1バイト目の値が省略されていて、 2バイト目以降のパラメータだけが配置されます (ランニング・ステータス・ルール)

また、トラックは、デルタタイムとイベントの組が並びます。 イベントの終端を判定しようとデルタタイムの先頭バイトを見ても、 最上位ビットは、デルタタイムの後続バイト有無を表すだけなので、 '0'の場合も'1'の場合もあり得ます

よってイベントの末尾は、イベントの種類ごとにまちまちで、 イベントの中身を解釈して判断するしかありません


SMFを読み込み鍵盤のオン・オフの情報を取り出してみる

イベントの種類による末尾判定の問題がありますが、まずは様子を見てみます

鍵盤オン・オフのイベントの形式

プログラム

prog_onoff.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_chord prog_onoff.c

$ ./prog_onoff < L3007_06.MID
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=94001
delta time=0
c1=0xff, c2=0x03
$

案の定、プログラムは鍵盤オン・オフ以外のイベントで停止しました

最初のイベントの1バイト目は 0xff、2バイト目は 0x03

メタイベントの場合の末尾は明確になりました

今の時点では、とりあえずメタイベントは読み飛ばしてスキップします

プログラム

prog_onoff2.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説 ]

$ gcc -o prog_onoff2 prog_onoff2.c

$ ./prog_onoff2 < L3007_06.MID
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=94001
delta time=0
meta event type=3 len=15 ...
delta time=0
meta event type=2 len=26 ...
delta time=0
meta event type=88 len=4 ...
delta time=0
meta event type=81 len=3 ...
delta time=0
c1=0xf0, c2=0x0a
$

プログラムは、鍵盤オン・オフ以外のイベントで停止しました

イベントの1バイト目は 0xf0、2バイト目は 0x0a

イベント末尾の判定材料として、長さの指定と終端がありますね?

少し中身を確認してみます

$ hd L3007_06.MID | head
00000000  4d 54 68 64 00 00 00 06  00 00 00 01 00 60 4d 54  |MThd.........`MT|
00000010  72 6b 00 01 6f 31 00 ff  03 0f 54 68 65 20 45 6e  |rk..o1....The En|
00000020  64 20 6f 66 20 41 73 69  61 00 ff 02 1a 28 43 29  |d of Asia....(C)|
00000030  31 39 39 33 20 52 6f 6c  61 6e 64 20 43 6f 72 70  |1993 Roland Corp|
00000040  6f 72 61 74 69 6f 6e 00  ff 58 04 04 02 18 08 00  |oration..X......|
00000050  ff 51 03 07 81 1b 00 f0  0a 41 10 42 12 40 00 7f  |.Q.......A.B.@..|
00000060  00 41 f7 30 f0 19 41 10  42 12 40 01 10 02 02 02  |.A.0..A.B.@.....|
00000070  02 02 02 02 02 02 02 02  02 00 00 00 00 17 f7 02  |................|
00000080  f0 0a 41 10 42 12 40 1a  15 02 0f f7 02 f0 0a 41  |..A.B.@........A|
00000090  10 42 12 40 01 33 60 2c  f7 2c b0 00 00 01 b0 20  |.B.@.3`,.,..... |

2バイト目のデータ長は、 3バイト目以降のデータのバイト数を表し、 かつデータの末尾は 0xf7 で終端してます

どちらで末尾を判定してもよさそうですが、 0xf7 の終端で判定した方が、256バイト以上の場合も対応できるので、 より好ましいのでなかろうか...

プログラム

prog_onoff3.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff3 prog_onoff3.c

$ ./prog_onoff3 < L3007_06.MID
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=94001
delta time=0
meta event type=3 len=15 ...
delta time=0
meta event type=2 len=26 ...
delta time=0
meta event type=88 len=4 ...
delta time=0
meta event type=81 len=3 ...
delta time=0
sys ex len=10 ...
delta time=48
sys ex len=25 ...
delta time=2
sys ex len=10 ...
delta time=2
sys ex len=10 ...
delta time=44
c1=0xb0, c2=0x00
$

プログラムは、鍵盤オン・オフ以外のイベントで停止しました

イベントの1バイト目は 0xb0、2バイト目は 0x00

ついでに、 1バイト目が 0xb0 かつ 2バイト目が 120以上の場合は、 チェンジ・モード・メッセージというイベントで、同様に

今の時点ではとりあえずスキップします

プログラム

prog_onoff4.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff4 prog_onoff4.c

$ ./prog_onoff4 < L3007_06.MID
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=94001
delta time=0
meta event type=3 len=15 ...
delta time=0
meta event type=2 len=26 ...
delta time=0
meta event type=88 len=4 ...
delta time=0
meta event type=81 len=3 ...
delta time=0
sys ex len=10 ...
delta time=48
sys ex len=25 ...
delta time=2
sys ex len=10 ...
delta time=2
sys ex len=10 ...
delta time=44
ctl chg ch=0 type=0 v=0
delta time=1
ctl chg ch=0 type=32 v=0
delta time=1
c1=0xc0, c2=0x32
$

プログラムは、鍵盤オン・オフ以外のイベントで停止しました

イベントの1バイト目は 0xc0、2バイト目は 0x32

今の時点ではとりあえずスキップします

(なかなか鍵盤オン・オフの情報に辿り着かない...)

プログラム

prog_onoff5.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff5 prog_onoff5.c

$ ./prog_onoff5 < L3007_06.MID
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=94001
delta time=0
meta event type=3 len=15 ...
delta time=0
meta event type=2 len=26 ...
delta time=0
meta event type=88 len=4 ...
delta time=0
meta event type=81 len=3 ...
delta time=0
sys ex len=10 ...
delta time=48
sys ex len=25 ...
delta time=2
sys ex len=10 ...
delta time=2
sys ex len=10 ...
delta time=44
ctl chg ch=0 type=0 v=0
delta time=1
ctl chg ch=0 type=32 v=0
delta time=1
prog num ch=0 v=50
delta time=1
ctl chg ch=0 type=7 v=90
delta time=0
  :
delta time=1
ctl chg ch=11 type=100 v=127
delta time=373
meta event type=88 len=4 ...
delta time=0
on ch=9 note=36 velo=90
delta time=0
on ch=9 note=42 velo=62
delta time=2
off ch=9 note=36 velo=64
delta time=0
off ch=9 note=42 velo=64
delta time=0
on ch=4 note=72 velo=55
delta time=10
ctl chg ch=4 type=11 v=43
delta time=12
ctl chg ch=4 type=11 v=47
delta time=12
ctl chg ch=4 type=11 v=51
delta time=11
on ch=9 note=42 velo=42
  :

ようやくブレーク・スルーです

目的の鍵盤オン・オフ情報が表示されました

^C で停止して、 tail コマンドで末尾部分だけ表示してみます

$ ./prog_onoff5 < L3007_06.MID | tail
delta time=2
off ch=9 note=42 velo=64
delta time=20
on ch=0 note=69 velo=103
delta time=1
on ch=0 note=76 velo=103
delta time=1
on ch=9 note=42 velo=62
delta time=1
c1=0xeb, c2=0x00
$

プログラムは、鍵盤オン・オフ以外のイベントで停止しました

イベントの1バイト目は 0xeb、2バイト目は 0x00

今の時点ではとりあえずスキップします

プログラム

prog_onoff6.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff6 prog_onoff6.c

$ ./prog_onoff6 < L3007_06.MID | tail
delta time=2
off ch=1 note=31 velo=64
delta time=0
off ch=2 note=91 velo=64
delta time=0
off ch=2 note=86 velo=64
delta time=17
off ch=8 note=31 velo=64
delta time=749
meta event type=47 len=0 ...
$

これで無事(?)ファイルの最後まで辿り着きました


SMFの鍵盤オン・オフ情報でSIN波の音を鳴らしてみる

ノート番号と周波数の関係

平均律で換算するので

周波数 = 440 × 2^( (ノート番号 - 69) / 12 )

C言語の関数なら

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

ですね

デルタタイムから時刻への換算

デルタタイムは、 直前のイベントから、今回のイベントまでの経過時間で、 数値を時間分解能で除算すると、四分音符の個数に換算可能です

よって、デルタタイムの合計を、ヘッダの分解能で割れば、 曲先頭からの四分音符の個数となります

四分音符と時間を関連付けるメタイベントのセット・テンポがあります

メタイベントとは

セット・テンポ

この四分音符一つあたりのマイクロ秒数を変数tempoとすると、 曲の先頭からの時刻(秒)は

デルタタイムの合計 ÷ ヘッダの分解能 × tempo × 10^(-6)

実際には曲の途中のメタイベントで、どんどんテンポが変更される事もあり、 複雑な計算になる場合もありえます

ここではとりあえず、鍵盤のオン・オフ情報だけを使って試すので、 四分音符一つあたり0.5秒固定とします

曲の先頭からの時刻(秒)は

デルタタイムの合計 ÷ ヘッダの分解能 × 0.5

プログラムの動作概略

プログラム実行

プログラム

prog_onoff_sin.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	int i, iv;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		v = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v += sin(2 * M_PI * freq * dsec);
		}
		v /= 16;
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
main()
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = 8000;
	smp_cnt = 0;
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

$ gcc -o prog_onoff_sin prog_onoff_sin.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin | play -t raw -r 8000 -b 8 -u -

prog_onoff_sin.mp3

とりあえず、なんとか、それらしく曲が再生できました

プチプチとノイズが多いです

原因の一つはドラム・パートの影響です

MIDIチャンネル9には、ノート番号毎にリズム楽器の音色が割り当てられ、使用されます

今の段階ではあきらめて、MIDIチャンネル9の演奏情報は使わないようにします

通常MIDIチャンネルは1から16の値をとり、ドラム・パートはチャンネル10とされています

ここでは、あえて鍵盤オン・オフのイベントの1バイト目の下位4ビットの値 0から15の値をそのまま、MIDIチャンネルと呼称してます

プログラム

prog_onoff_sin2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	int i, iv;

	if(ch == 9) return;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		v = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v += sin(2 * M_PI * freq * dsec);
		}
		v /= 16;
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
main()
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = 8000;
	smp_cnt = 0;
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin2 prog_onoff_sin2.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin2 | play -t raw -r 8000 -b 8 -u -

prog_onoff_sin2.mp3

これで多少ノイズが減りました


SMFのイベントとデータ長

ここで、ここまで扱ったイベントの種類とデータ長を整理してみます

1バイト目の上位4ビットの値 データ長 内容
8 3バイト 鍵盤オフ (note off)
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はノート番号で鍵盤の位置を表し0から127の値
3バイト目は鍵盤を動かす速さ(強さ)を表し0から127の値
9 3バイト 鍵盤オン (note on)
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はノート番号で鍵盤の位置を表し0から127の値
3バイト目は鍵盤を動かす速さ(強さ)を表し0から127の値
0xb 3バイト 2バイト目の値が120未満 コントロール・チェンジ
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
3バイト目はパラメータ
2バイト目の値が120以上 チェンジ・モード・メッセージ
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
3バイト目はパラメータ
0xc 2バイト プログラム番号イベント
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はプログラム番号を表し0から127の値
0xe 3バイト pitch wheel change イベント
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はパラメータの下位7ビット
3バイト目はパラメータの上位7ビット
0xf 可変 1バイト目の下位4ビットが0 システム・エクスクルーシブ・メッセージ
2バイト目がデータ長
(3バイト目以降のバイト数で終端の 0xf7 分も含む)
イベント末尾のバイトは 0xf7 で終端
1バイト目の下位4ビットが0xf メタイベント
2バイト目がイベントタイプ
3バイト目がデータ長
(4バイト目以降に続くバイト数)

イベントの1バイト目の最上位ビットは'1'なので、 1バイト目の上位4ビットの値は、8から 0xf までです

ターゲットのSMFでは使用されていませんでしたが、 抜け部分を埋めておきます

1バイト目の上位4ビットの値 データ長 内容
8 3バイト 鍵盤オフ (note off)
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はノート番号で鍵盤の位置を表し0から127の値
3バイト目は鍵盤を動かす速さ(強さ)を表し0から127の値
9 3バイト 鍵盤オン (note on)
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はノート番号で鍵盤の位置を表し0から127の値
3バイト目は鍵盤を動かす速さ(強さ)を表し0から127の値
0xa 3バイト キー・プレッシャー (アフター・タッチ)
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はノート番号で鍵盤の位置を表し0から127の値
3バイト目は鍵盤を押える強さを表し0から127の値
0xb 3バイト 2バイト目の値が120未満 コントロール・チェンジ
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
3バイト目はパラメータ
2バイト目の値が120以上 チェンジ・モード・メッセージ
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
3バイト目はパラメータ
0xc 2バイト プログラム番号イベント
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はプログラム番号を表し0から127の値
0xd 2バイト チャンネル・プレッシャー
(チャンネル・アフター・タッチ)
(鍵盤毎ではなくそのチャンネル全体に指定)
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目は鍵盤を押える強さを表し0から127の値
0xe 3バイト pitch wheel change イベント
1バイト目の下位4ビットはMIDIチャンネルを表し0から15の値
2バイト目はパラメータの下位7ビット
3バイト目はパラメータの上位7ビット
0xf 可変 1バイト目の下位4ビットが0 システム・エクスクルーシブ・メッセージ
2バイト目がデータ長
(3バイト目以降のバイト数で終端の 0xf7 分も含む)
イベント末尾のバイトは 0xf7 で終端
2バイト 1バイト目の下位4ビットが1 MIDIタイム・コード
2バイト目は0から127の値
3バイト 1バイト目の下位4ビットが2 ソング・ポジション
2バイト目はパラメータの下位7ビット
3バイト目はパラメータの上位7ビット
2バイト 1バイト目の下位4ビットが3 ソング番号
2バイト目はソング番号で0から127の値
未定義 1バイト目の下位4ビットが4 未定義
未定義 1バイト目の下位4ビットが5 未定義
1バイト 1バイト目の下位4ビットが6 チューン・リクエスト
... 1バイト目の下位4ビットが7 システム・エクスクルーシブ・メッセージの終端用
1バイト 1バイト目の下位4ビットが8 MIDIクロック
未定義 1バイト目の下位4ビットが9 未定義
1バイト 1バイト目の下位4ビットが0xa シーケンス開始
1バイト 1バイト目の下位4ビットが0xb シーケンス再開
1バイト 1バイト目の下位4ビットが0xc シーケンス停止
未定義 1バイト目の下位4ビットが0xd 未定義
1バイト 1バイト目の下位4ビットが0xe アクティブ・センシング
可変 1バイト目の下位4ビットが0xf メタイベント
2バイト目がイベントタイプ
3バイト目がデータ長
(4バイト目以降に続くバイト数)

ターゲットのSMFに含まれてませんが、今の段階ではスキップするだけなので、 とりあえずプログラムに反映しておきます

プログラム

prog_onoff_sin3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	int i, iv;

	if(ch == 9) return;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		v = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v += sin(2 * M_PI * freq * dsec);
		}
		v /= 16;
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
main()
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = 8000;
	smp_cnt = 0;
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説


音のPCMデータ形式の変更

ここまで、出力する音のPCMデータ形式は

固定としてきました

データサイズが少ない分、はっきり言って音質は良くないです

サンプリング周波数の変更

まず、8 kHzのサンプリング周波数ですが、半分の 4 kHz 以下の音しか復元できません。(サンプリング定理)

人の可聴範囲は 20 Hz から 20 kHz と言われ、音楽CDのサンプリング周波数は 44100 Hz です。

サンプリング周波数を可変にしてみます

サンプリング周波数の指定は、main()関数の冒頭で、 変数 smp_freq に値 8000 を設定してました

	  :
int
main()
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = 8000;
	smp_cnt = 0;
	note_buf_init();
	  :

プログラム起動時のコマンドライン・パラメータで指定できるように変更してみます。

指定方法は、playコマンド(soxコマンド)での指定にならって、

-r <サンプリング周波数>

とします

プログラム

prog_onoff_sin4.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	int i, iv;

	if(ch == 9) return;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		v = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v += sin(2 * M_PI * freq * dsec);
		}
		v /= 16;
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
opt_int(char *key, int ac, char **av, int def)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) break;
	if(i+1 >= ac) return def;
	return strtol(av[i+1], NULL, 0);
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = opt_int("-r", ac, av, 8000);
	smp_cnt = 0;
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin4 prog_onoff_sin4.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin4 > r8000.raw
$ cat L3007_06.MID | ./prog_onoff_sin4 -r 44100 > r44100.raw

$ ls -l *.raw
-rw-rw-r-- 1 kondoh root 15703965 Aug  5  2013 r44100.raw
-rw-rw-r-- 1 kondoh root  2848792 Aug  5  2013 r8000.raw
$

2848792 / 8000 * 44100 = 15703965.9

ファイルのサイズは合ってるようです

$ cat r8000.raw | play -t raw -r 8000 -b 8 -u -
$ cat r44100.raw | play -t raw -r 44100 -b 8 -u -

として聴き比べてみると、多少、高音が改善されました

これまで同様、プログラム prog_onoff_sin4 の処理速度が十分速ければ、ファイルに落すまでもなく、 次のコマンドで実行可能です

$ cat L3007_06.MID | ./prog_onoff_sin4 -r 44100 | play -t raw -r 44100 -b 8 -u -

WAV形式に落すには、soxコマンドを使用し、同様にサンプリング周波数を指定します

$ cat L3007_06.MID | ./prog_onoff_sin4 -r 44100 | sox -t raw -r 44100 -b 8 -u - prog_onoff_sin4.wav

prog_onoff_sin4.mp3

ビット長の変更

次に、データのビット長を 8 bit 固定してましたが、これも音質が悪い原因の一つです。

音楽CDでは、ビット長は 16 bit です。

16 bit でも出力出来るようにしてみます

8 bit から 16bit、つまり 1 バイトから 2 バイトになると、 そのデータにバイト・オーダが生じます。(?)

さて、ビッグエンディアンで出力すべきか、リトルエンディアン出力すべきか?

playコマンド(soxコマンド)のオプション指定では

-L : リトルエンディアン
-B : ビッグエンディアン

として、明示的に指定できるようです

と言うことは、プログラムからどちらの形式で出力しても、 そのデータを扱う playコマンド(soxコマンド) で、 -L あるいは -B を指定すれば、正しく解釈してくれるはずです

とは言え、いちいち意識して指定するのも面倒なものです。

-L も -B も指定しなかった時のデフォルトの扱いは、どちらでしょうか?

簡単なプログラムで確かめてみましょう。 以前のSIN波を出力するプログラム prog_sin.c を改造して試してみます

#include <stdio.h>
#include <math.h>

int
main()
{
 int smp_freq = 8000;
 int smp_cnt, iv;
 double freq = 880;
 double len = 1.0;
 double sec, v;

 smp_cnt = 0;
 do{
  sec = (double)smp_cnt / smp_freq;
  v = sin(2* M_PI * freq * sec);
  iv = 128 + (int)(v * 127);
  putchar(iv);
  smp_cnt++;
 }while(sec < len);
 return 0;
}

/* EOF */

このプログラムの

iv = 128 + (int)(v * 127);
putchar(iv);

の箇所が、符号なしの 8 ビットのデータとして出力してる箇所です。

この2行を

iv = 32768 + (int)(v * 32767);
putchar(iv & 0xff)
putchar((iv >> 8) & 0xff)

とすれば、符号なしの 16ビットデータのリトルエンディアンとして出力

iv = 32768 + (int)(v * 32767);
putchar((iv >> 8) & 0xff)
putchar(iv & 0xff)

とすれば、符号なしの 16ビットデータのビッグエンディアンとして出力 するはずです。

2つのプログラムにして試してみます

プログラム

prog_sin16_le.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int smp_freq = 8000;
	int smp_cnt, iv;
	double freq = 880;
	double len = 1.0;
	double sec, v;

	smp_cnt = 0;
	do{
		sec = (double)smp_cnt / smp_freq;
		v = sin(2* M_PI * freq * sec);
		iv = 32768 + (int)(v * 32767);
		putchar(iv & 0xff);
		putchar((iv >> 8) & 0xff);
		smp_cnt++;
	}while(sec < len);
	return 0;
}

/* EOF */

プログラム

prog_sin16_be.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int smp_freq = 8000;
	int smp_cnt, iv;
	double freq = 880;
	double len = 1.0;
	double sec, v;

	smp_cnt = 0;
	do{
		sec = (double)smp_cnt / smp_freq;
		v = sin(2* M_PI * freq * sec);
		iv = 32768 + (int)(v * 32767);
		putchar((iv >> 8) & 0xff);
		putchar(iv & 0xff);
		smp_cnt++;
	}while(sec < len);
	return 0;
}

/* EOF */

$ gcc -o prog_sin16_le prog_sin16_le.c -lm
$ gcc -o prog_sin16_be prog_sin16_be.c -lm

まずはリトルエンディアンから。

リトルエンディアンで生成して、playコマンドでリトルエンディアンで受けます

playコマンドのオプション

-b 8   : ビット長8ビット
-b 16  : ビット長16ビット

  Sox v14.0.1 の場合
  -b : ビット長8ビット
  -w : ビット長16ビット

より

$ ./prog_sin16_le | play -t raw -r 8000 -b 16 -L -u -

  あるいは
  $ ./prog_sin16_le | play -t raw -r 8000 -w -L -u -

正常に音が鳴ります

ビッグエンディアンで生成して、playコマンドでビッグエンディアンで受けます

$ ./prog_sin16_be | play -t raw -r 8000 -b 16 -B -u -

  あるいは
  $ ./prog_sin16_be | play -t raw -r 8000 -w -B -u -

正常に音が鳴ります

リトルエンディアンで生成して、playコマンドでエンディアン指定なしで受けます

$ ./prog_sin16_le | play -t raw -r 8000 -b 16 -u -

  あるいは
  $ ./prog_sin16_le | play -t raw -r 8000 -w -u -

正常に音が鳴ります

ということは、デフォルトはリトルエンディアン ?

あるいは、実行するマシンのCPUのエンディアンに依存しているのかもしれませんね

ビッグエンディアンで生成して、playコマンドでエンディアン指定なしで受けます

(雑音が出るのでボリュームを絞って試してみましょう...)

$ ./prog_sin16_be | play -t raw -r 8000 -b 16 -u -

  あるいは
  $ ./prog_sin16_be | play -t raw -r 8000 -w -u -

確かに、正常な場合の音とは違います

ということで、CPUのエンディアンに依存するのかも知れませんが、 とりあえず、リトルエンディアン固定でよさそうですね

さて、ビット長の指定に戻ります

指定方法は、playコマンド(soxコマンド)での指定にならって、

-b 8   : ビット長8ビット
-b 16  : ビット長16ビット

とします

プログラム

prog_onoff_sin5.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;
int bit_len;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	int i, iv;

	if(ch == 9) return;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		v = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v += sin(2 * M_PI * freq * dsec);
		}
		v /= 16;
		switch(bit_len){
		case 8:
			iv = 128 + (int)(v * 127);
			putchar(iv);
			break;
		case 16:
			iv = 32768 + (int)(v * 32767);
			putchar(iv & 0xff);
			putchar((iv >> 8) & 0xff);
			break;
		}
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
opt_int(char *key, int ac, char **av, int def)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) break;
	if(i+1 >= ac) return def;
	return strtol(av[i+1], NULL, 0);
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = opt_int("-r", ac, av, 8000);
	smp_cnt = 0;
	bit_len = opt_int("-b", ac, av, 8);
	if(bit_len != 8 && bit_len != 16) ERR("bit_len");
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin5 prog_onoff_sin5.c -lm

サンプリング周波数 8000 Hz, ビット長 8 bit
$ cat L3007_06.MID | ./prog_onoff_sin5 | play -t raw -r 8000 -b 8 -u -

サンプリング周波数 8000 Hz, ビット長 16 bit
$ cat L3007_06.MID | ./prog_onoff_sin5 -b 16 | play -t raw -r 8000 -b 16 -u -

サンプリング周波数 44100 Hz, ビット長 8 bit
$ cat L3007_06.MID | ./prog_onoff_sin5 -r 44100 | play -t raw -r 44100 -b 8 -u -

サンプリング周波数 44100 Hz, ビット長 16 bit
$ cat L3007_06.MID | ./prog_onoff_sin5 -r 44100 -b 16 | play -t raw -r 44100 -b 16 -u -

prog_onoff_sin5.mp3

サンプリング周波数の変更ほどの効果は感じませんが、多少、音質が良くなってると思います

符号有無の変更

音質には関係しませんが、符号なしの形式に固定してたので、 ついでに符号ありの形式でも出力できるようにしておきます

playコマンド(soxコマンド)では、次のオプション・パラメータで、符号有無の形式を指定します

(ただし、ビット長が 8 bit の場合は、符号なし取り扱うのが通例のため、 8 bit符号ありの組み合わせを指定しても、反映されないようです)

-u     : 符号なし
-s     : 符号あり

  既に廃止予定のエイリアスになっていて、次の指定が正式のようです...
  --e unsigned-integer : 符号なし
  --e signed-integer   : 符号あり

指定方法は、playコマンド(soxコマンド)での指定にならって、

-u     : 符号なし
-s     : 符号あり

とし、省略した場合は、符号なしとします

プログラム

prog_onoff_sin6.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;
int bit_len;
int sign;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	int i, iv;
	int base, amp;
	int iv_min, iv_max;

	if(ch == 9) return;

	amp = bit_len == 8 ? 127 : 32767;
	base = sign ? 0 : amp + 1;
	iv_min = base - (amp + 1);
	iv_max = base + amp;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		v = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v += sin(2 * M_PI * freq * dsec);
		}
		v /= 16;
		iv = base + (int)(v * amp);
		if(iv > iv_max) iv = iv_max;
		if(iv < iv_min) iv = iv_min;
		switch(bit_len){
		case 8:
			putchar(iv);
			break;
		case 16:
			putchar(iv & 0xff);
			putchar((iv >> 8) & 0xff);
			break;
		}
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

int
opt_int(char *key, int ac, char **av, int def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return strtol(av[i+1], NULL, 0);
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = opt_int("-r", ac, av, 8000);
	smp_cnt = 0;
	bit_len = opt_int("-b", ac, av, 8);
	if(bit_len != 8 && bit_len != 16) ERR("bit_len");
	sign = (opt_idx("-s", ac, av) >= 0);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin6 prog_onoff_sin6.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin6 -r 44100 -b 16 -s | play -t raw -r 44100 -b 16 -s -

prog_onoff_sin6.mp3

チャンネル数の変更

音のPCMデータ形式について、残るはモノラル固定です。

チャンネル数1つで試してきましたが、 ステレオにして、左右2つのチャンネルを試してみます

といっても、今のところ鍵盤オン・オフの情報しか使ってませんので、 ここでは、適当に左右に振り分けれるか、試してみるだけに留めます

playコマンド(soxコマンド)では、

-c 1   : モノラル (デフォルト)
-c 2   : ステレオ

で指定し、デフォルトはモノラルです

指定方法は、例によって、上記に合わせておきます

さて、チャンネルを1から2にすると、これまでの1つ分のデータが、 左右2つ分のデータの並びに変わるのでしょうが...

先のデータ、後のデータ、どちらが右で、どちらが左でしょううか?

まずは、簡単なプログラムで確かめてみます

既出のプログラム

prog_sin.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int smp_freq = 8000;
	int smp_cnt, iv;
	double freq = 880;
	double len = 1.0;
	double sec, v;

	smp_cnt = 0;
	do{
		sec = (double)smp_cnt / smp_freq;
		v = sin(2* M_PI * freq * sec);
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}while(sec < len);
	return 0;
}

/* EOF */
#include <stdio.h>
#include <math.h>

int
main()
{
	int smp_freq = 8000;
	int smp_cnt, iv;
	double freq = 880;
	double len = 1.0;
	double sec, v;

	smp_cnt = 0;
	do{
		sec = (double)smp_cnt / smp_freq;
		v = sin(2* M_PI * freq * sec);
		iv = 128 + (int)(v * 127);
		putchar(iv);
		smp_cnt++;
	}while(sec < len);
	return 0;
}

/* EOF */

を実行すると

880 Hz の音 1 秒分の RAWデータが出力されます

この出力を左右に割り振るだけのプログラムで試してみます

プログラム

prog_pan.c
#include <stdio.h>

int
main()
{
	int ch;

	while((ch = getchar()) != EOF){
		putchar(ch);
		putchar(0);
	}
	return 0;
}

/* EOF */

$ gcc -o prog_pan prog_pan.c

$ ./prog_sin | ./prog_pan | play -t raw -r 8000 -b 8 -u -c 2 -

play コマンドへのパラメータ -c 2 が、チャンネル数 2 の指定です

左から「ポー」と聞こえました

逆も試してみます

プログラム

prog_pan2.c
#include <stdio.h>

int
main()
{
	int ch;

	while((ch = getchar()) != EOF){
		putchar(0);
		putchar(ch);
	}
	return 0;
}

/* EOF */

$ gcc -o prog_pan2 prog_pan2.c

$ ./prog_sin | ./prog_pan2 | play -t raw -r 8000 -b 8 -u -c 2 -

今度は右から「ポー」と聞こえました

チャンネル数 2 の場合は、左、右 の順ですね

さて、SMFの鍵盤オン・オフ情報だけから波形を生成するプログラムで、 どう試すべしでしょうか...?

とりあえず、MIDIチャンネルの値 0 から 15 で振り分けて試してみます。

MIDIチャンネル 0 が一番左側、MIDIチャンネル 15 が一番右側として、 間は均等に並べてみます

プログラム

prog_onoff_sin7.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	double on_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;
int bit_len;
int sign;
int ch_num;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if(note_buf[i].ch == ch &&
		   (ch < 0 || note_buf[i].note == note)) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

struct out_rec{
	int base;
	int amp;
	int iv_min;
	int iv_max;
} otr;

void
out_init(struct out_rec *ot)
{
	ot->amp = bit_len == 8 ? 127 : 32767;
	ot->base = sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(bit_len){
	case 8:
		putchar(iv);
		break;
	case 16:
		putchar(iv & 0xff);
		putchar((iv >> 8) & 0xff);
		break;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i;

	if(ch == 9) return;

	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			if(ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(&otr, vl / 16);
		if(ch_num > 1) out_do(&otr, vr / 16);
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, 0)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note)) < 0) return;
		note_buf_free(i);
	}
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

int
opt_int(char *key, int ac, char **av, int def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return strtol(av[i+1], NULL, 0);
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = opt_int("-r", ac, av, 8000);
	smp_cnt = 0;
	bit_len = opt_int("-b", ac, av, 8);
	if(bit_len != 8 && bit_len != 16) ERR("bit_len");
	sign = (opt_idx("-s", ac, av) >= 0);
	ch_num = opt_int("-c", ac, av, 1);
	out_init(&otr);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin7 prog_onoff_sin7.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin7 -r 44100 -b 16 -s | play -t raw -r 44100 -b 16 -s -
(まずは、従来通り動作するか確認)

$ cat L3007_06.MID | ./prog_onoff_sin7 -r 44100 -b 16 -s -c 2 | play -t raw -r 44100 -b 16 -s -c 2 -
(チャンネル数2 "-c 2" の指定を試します)

prog_onoff_sin7_xaa.mp3

prog_onoff_sin7_xab.mp3

使ってないMIDIチャンネルもあるので、左右のバランスは適当ですが、 意図した通り、左右に割り振られて音が鳴ってます


せめて残響音の風味

ここまでで、鳴らしてきた音は、鍵盤オンからオフの間、一定の振幅のサイン波です。

鍵盤オフ・イベントで、プツっと音が途切れます。 あまりにも、そっけないです。(T_T)

せめて残響音が残ってる風味をつけてみましょう

鍵盤オンから鍵盤オフまでは、同様に音を鳴らしますが、 鍵盤オフからしばらくの間、減衰させながら音を鳴らし続けてみます。

いわゆるエンベロープのリリース・タイムを持たせてみます

ここまでの処理では、

としてきました

リリース・タイムを持たせるには、 鍵盤をオフにしてからも、しばらく音を鳴らしたいので、 鍵盤オフ・イベントがきても、バッファの情報は削除せず、 鍵盤オフの状態に変化させて、存続させるようにします

鍵盤オフになってからは、振幅を減衰させた波形を生成し、 リリース・タイムとして設定した時間を超過してから、バッファの情報を削除するようにします

プログラム

prog_onoff_sin8.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
} note_buf[NOTE_BUF_N];

int smp_freq, smp_cnt;
int bit_len;
int sign;
int ch_num;

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

struct out_rec{
	int base;
	int amp;
	int iv_min;
	int iv_max;
} otr;

void
out_init(struct out_rec *ot)
{
	ot->amp = bit_len == 8 ? 127 : 32767;
	ot->base = sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(bit_len){
	case 8:
		putchar(iv);
		break;
	case 16:
		putchar(iv & 0xff);
		putchar((iv >> 8) & 0xff);
		break;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	if(ch == 9) return;

	release = 0.3;
	while((sec = (double)smp_cnt / smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(&otr, vl / 16);
		if(ch_num > 1) out_do(&otr, vr / 16);
		smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

int
opt_int(char *key, int ac, char **av, int def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return strtol(av[i+1], NULL, 0);
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;

	smp_freq = opt_int("-r", ac, av, 8000);
	smp_cnt = 0;
	bit_len = opt_int("-b", ac, av, 8);
	if(bit_len != 8 && bit_len != 16) ERR("bit_len");
	sign = (opt_idx("-s", ac, av) >= 0);
	ch_num = opt_int("-c", ac, av, 1);
	out_init(&otr);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin8 prog_onoff_sin8.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin8 -r 44100 -b 16 -s -c 2 | play -t raw -r 44100 -b 16 -s -c 2 -

prog_onoff_sin8_xaa.mp3

prog_onoff_sin8_xab.mp3

どうでしょう?

プチプチ感が随分緩和されたように聴こえます


コマンドの内部起動

今回は、少し違う角度からのアプローチです。

プログラムを起動して試すときの、コマンドを見直してみます

$ cat L3007_06.MID | ./prog_onoff_sin8 > /tmp/sin8.raw

(サンプリング周波数 8000 Hz、ビット長 8 bit、符号なし、モノラルで、 波形を生成して、/tmp/sin8.raw ファイルに落す)

$ cat L3007_06.MID | ./prog_onoff_sin8 | play -t raw -r 8000 -b 8 -u -c 1 -

(サンプリング周波数 8000 Hz、ビット長 8 bit、符号なし、モノラルで、 波形を生成して、playコマンドで再生)

$ cat L3007_06.MID | ./prog_onoff_sin8 -r 44100 -b 16 -s -c 2 | sox -t raw -r 44100 -b 16 -s -c 2 - /tmp/sin8.wav

(サンプリング周波数 44100 Hz、ビット長 16 bit、符号あり、ステレオで、 波形を生成して、soxコマンドで、/tmp/sin8.wav ファイルに落す)

$ play /tmp/sin8.wav

(ファイル /tmp/sin8.wav を再生)

パイプでplayコマンドや、soxコマンドを接続して実行するときに、 波形を生成する側のデータ形式に合わせて、パラメータを指定してます。

同じパラメータを繰り返し与える場合も多くて、面倒ですね。

タイプミスしようものなら、ひどいノイズが鳴る場合もあります

こういう面倒な事は、機械にさせるべきでしょう。

波形を生成する側のプログラムは、自分が出力するデータ形式を知ってます。(当然)

なので、波形を生成するプログラムが起動してから、内部的にplayコマンドや、soxコマンドを起動する方式にすれば、なんとかなりそうです。

波形生成プログラムが、playコマンドや、soxコマンドの起動パラメータを、合致するように与えればいいはずです。

波形生成プログラムから、playコマンドを起動したとして、 波形生成プログラムの標準出力を、起動させたplayコマンドの標準入力にパイプで接続できるでしょうか?

標準ライブラリの popen() が、まさにそれです

詳細は man popen にまかせるとして、(...なんか響きがサザン的)

簡単な例で試してみます

プログラム

prog_256.c
#include <stdio.h>

int
main()
{
	int i;

	for(i=0; i<256; i++) putchar(i);
	return 0;
}

/* EOF */

まずは、0 から 255 までのバイナリを出力するだけのプログラムです。

このプログラムの出力を、パイプで hdコマンド (ヘキサダンプ) に接続すると

$ gcc -o prog_256 prog_256.c

$ ./prog_256 | hd
00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|
00000010  10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f  |................|
00000020  20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f  | !"#$%&'()*+,-./|
00000030  30 31 32 33 34 35 36 37  38 39 3a 3b 3c 3d 3e 3f  |0123456789:;<=>?|
00000040  40 41 42 43 44 45 46 47  48 49 4a 4b 4c 4d 4e 4f  |@ABCDEFGHIJKLMNO|
00000050  50 51 52 53 54 55 56 57  58 59 5a 5b 5c 5d 5e 5f  |PQRSTUVWXYZ[\]^_|
00000060  60 61 62 63 64 65 66 67  68 69 6a 6b 6c 6d 6e 6f  |`abcdefghijklmno|
00000070  70 71 72 73 74 75 76 77  78 79 7a 7b 7c 7d 7e 7f  |pqrstuvwxyz{|}~.|
00000080  80 81 82 83 84 85 86 87  88 89 8a 8b 8c 8d 8e 8f  |................|
00000090  90 91 92 93 94 95 96 97  98 99 9a 9b 9c 9d 9e 9f  |................|
000000a0  a0 a1 a2 a3 a4 a5 a6 a7  a8 a9 aa ab ac ad ae af  |................|
000000b0  b0 b1 b2 b3 b4 b5 b6 b7  b8 b9 ba bb bc bd be bf  |................|
000000c0  c0 c1 c2 c3 c4 c5 c6 c7  c8 c9 ca cb cc cd ce cf  |................|
000000d0  d0 d1 d2 d3 d4 d5 d6 d7  d8 d9 da db dc dd de df  |................|
000000e0  e0 e1 e2 e3 e4 e5 e6 e7  e8 e9 ea eb ec ed ee ef  |................|
000000f0  f0 f1 f2 f3 f4 f5 f6 f7  f8 f9 fa fb fc fd fe ff  |................|
00000100
$

次に、バイナリを出力するプログラムから hd コマンドを popen() で起動して、 hd コマンドの標準入力に向かって、バイナリを出力してみます

プログラム

prog_256_popen.c
#include <stdio.h>

int
main()
{
	int i;
	FILE *fp;

	if((fp = popen("hd", "w")) == NULL){
		fprintf(stderr, "err popen");
		return -1;
	}
	for(i=0; i<256; i++) fputc(i, fp);
	pclose(fp);
	return 0;
}

/* EOF */

popen() 関数で "hd" コマンドを起動してます。

モード "w" の指定では、起動したコマンドの標準入力が、 パイプで接続され、ファイルポインタで返ります。

バイナリの出力を、標準出力ではなく、 popen() 関数で返った、ファイルポインタに向けて出力します。

最後に pclose()。

$ gcc -o prog_256_popen prog_256_popen.c

$ ./prog_256_popen
00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|
00000010  10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f  |................|
00000020  20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f  | !"#$%&'()*+,-./|
00000030  30 31 32 33 34 35 36 37  38 39 3a 3b 3c 3d 3e 3f  |0123456789:;<=>?|
00000040  40 41 42 43 44 45 46 47  48 49 4a 4b 4c 4d 4e 4f  |@ABCDEFGHIJKLMNO|
00000050  50 51 52 53 54 55 56 57  58 59 5a 5b 5c 5d 5e 5f  |PQRSTUVWXYZ[\]^_|
00000060  60 61 62 63 64 65 66 67  68 69 6a 6b 6c 6d 6e 6f  |`abcdefghijklmno|
00000070  70 71 72 73 74 75 76 77  78 79 7a 7b 7c 7d 7e 7f  |pqrstuvwxyz{|}~.|
00000080  80 81 82 83 84 85 86 87  88 89 8a 8b 8c 8d 8e 8f  |................|
00000090  90 91 92 93 94 95 96 97  98 99 9a 9b 9c 9d 9e 9f  |................|
000000a0  a0 a1 a2 a3 a4 a5 a6 a7  a8 a9 aa ab ac ad ae af  |................|
000000b0  b0 b1 b2 b3 b4 b5 b6 b7  b8 b9 ba bb bc bd be bf  |................|
000000c0  c0 c1 c2 c3 c4 c5 c6 c7  c8 c9 ca cb cc cd ce cf  |................|
000000d0  d0 d1 d2 d3 d4 d5 d6 d7  d8 d9 da db dc dd de df  |................|
000000e0  e0 e1 e2 e3 e4 e5 e6 e7  e8 e9 ea eb ec ed ee ef  |................|
000000f0  f0 f1 f2 f3 f4 f5 f6 f7  f8 f9 fa fb fc fd fe ff  |................|
00000100
$

という事で、波形を生成するプログラムから、playコマンドや、soxコマンドを起動するようにしてみます

と、その前に、出力データ形式関連の変数などを、構造体にまとめるなどして、少々整理しておきます。

プログラム

prog_onoff_sin9.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

int
opt_int(char *key, int ac, char **av, int def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return strtol(av[i+1], NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		putchar(iv);
		break;
	case 16:
		putchar(iv & 0xff);
		putchar((iv >> 8) & 0xff);
		break;
	}
}

void
note_onoff(struct out_rec *ot, int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	if(ch == 9) return;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(&otr, hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin9 prog_onoff_sin9.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin9 -r 44100 -b 16 -s -c 2 > sin9.raw
$ cat L3007_06.MID | ./prog_onoff_sin8 -r 44100 -b 16 -s -c 2 > sin8.raw

$ cmp sin8.raw sin9.raw
$

(以前と同様に動作する事を確認)

データ出力まわりを整理した上で、次に popen() を使うよう変更してみます。

次のオプション指定を追加します

-play                 : 内部でplayコマンドを起動する
-sox <出力ファイル名> : 内部でsoxコマンドを起動する

プログラム

prog_onoff_sin10.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

void
note_onoff(struct out_rec *ot, int onoff, double evt_sec, int ch, int note)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	if(ch == 9) return;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			rd(); /* skip velo */
			note_onoff(&otr, hi == 9, sec, low, note);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin10 prog_onoff_sin10.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin10 -sox sin10.wav
$ cat L3007_06.MID | ./prog_onoff_sin9 | sox -t raw -r 8000 -b 8 -u - sin9.wav

$ cmp sin9.wav sin10.wav
$

(WAVファイルの中身、一致)

これで

$ cat L3007_06.MID | ./prog_onoff_sin9 -r 44100 -b 16 -s -c 2 | play -t raw -r 44100 -b 16 -s -c 2 -

の指定も

$ cat L3007_06.MID | ./prog_onoff_sin10 -r 44100 -b 16 -s -c 2 -play

で済みます


鍵盤を押す強さ

鍵盤のオン・オフの情報で波形を生成してきましたが、 まだ鍵盤のオン・オフのイベントの情報を使いきれてません

鍵盤オン・オフのイベントの形式

3バイト目の情報が手つかずで反映されてません

演奏のタッチの強弱は無視され、一定の音量で再生するので、 のっぺりとした感じになってます。

せっかく鍵盤オン・オフのイベントに含まれている情報なので、 反映させてみましょう

鍵盤オン・イベントは、鍵盤を押す強さととらえると直感的に理解できますが、 鍵盤オフ・イベントの強さとは ?

鍵盤をオフする方向に動かすときの速度という事ですが、 音の消え方に、あまり大きな違いが出るように思えません。

鍵盤オン・イベントの方だけ、反映します

鍵盤情報を記録するバッファに、強さの情報を追加して、 生成するSIN波の振幅に反映します

プログラム

prog_onoff_sin11.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

void
note_onoff(struct out_rec *ot, int onoff, double evt_sec, int ch, int note, int velo)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	if(ch == 9) return;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(&otr, hi == 9, sec, low, note, velo);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin11 prog_onoff_sin11.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin11 -r 44100 -b 16 -s -c 2 -play

prog_onoff_sin11_xaa.mp3

prog_onoff_sin11_xab.mp3

どうでしょうか? うーむむむ...

このSMFでは、 YMO のライブアルバル「Public Pressure」での曲の再現を目指してるようです。

ですが、そもそも当時の演奏で使用したアナログ・シンセサイザに、ベロシティの機能があったのか怪しいですね

SMFのデータそのものを、ちょっと確認してみましょう。

以前に、SMFを読み込み鍵盤のオン・オフの情報を取り出してみたときのプログラム

prog_onoff6.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

を使って、鍵盤オン・イベントの中身を覗いてみましょう

$ gcc -o prog_onoff6 prog_onoff6.c

$ cat L3007_06.MID | ./prog_onoff6 | grep '^on ' | sed -e '/ch=9 /d' -e 's/note=[0-9]* //' | sort | uniq
on ch=0 velo=103
on ch=0 velo=64
on ch=0 velo=78
on ch=0 velo=84
on ch=0 velo=88
on ch=0 velo=93
on ch=0 velo=95
on ch=0 velo=98
on ch=1 velo=101
on ch=1 velo=102
on ch=1 velo=103
on ch=1 velo=105
on ch=1 velo=108
on ch=1 velo=111
  :
on ch=8 velo=90
on ch=8 velo=92
on ch=8 velo=95
$

確かに、データ的には強弱がありました。

MIDIチャンネル 3 が主旋律っぽいので、後半のソロのあたりで違いが分かるかも知れません


MIDIチャンネルごとのボリューム

鍵盤オンの強さで音量を変えれました。

音量つながりという事で、MIDIチャンネルごとの音量を設定してみます

これまで無視してきた、コントロール・チェンジ・イベントの中に、 MIDIチャンネルごとの音量を設定するためのイベントがあります

コントロール・チェンジ・イベント

この、2バイト目のタイプの値が、

となります。

それぞれ 3バイト目に、7ビットの 0 から 127 の値が指定されます。

MSB は上位の 7 ビット分、LSB は下位の 7 ビット分、合計 14ビットの値となります

処理の概略

まず、ターゲットの SMF の、チャンネル・ボリュームのイベントを覗いてみます。

例によって、SMFを読み込み鍵盤のオン・オフの情報を取り出してみたときのプログラム

prog_onoff6.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

を使ってみます

$ gcc -o prog_onoff6 prog_onoff6.c

$ cat L3007_06.MID | ./prog_onoff6 | grep 'ctl chg' | grep 'type=7 '
ctl chg ch=0 type=7 v=90
ctl chg ch=1 type=7 v=125
ctl chg ch=2 type=7 v=80
ctl chg ch=3 type=7 v=110
ctl chg ch=4 type=7 v=80
ctl chg ch=5 type=7 v=80
ctl chg ch=6 type=7 v=70
ctl chg ch=7 type=7 v=70
ctl chg ch=8 type=7 v=65
ctl chg ch=9 type=7 v=120
ctl chg ch=10 type=7 v=100
ctl chg ch=11 type=7 v=100
$

$ cat L3007_06.MID | ./prog_onoff6 | grep 'ctl chg' | grep 'type=39 '
$

コントロール・チェンジ・イベントの中で、 2バイト目が 7 の チャンネル・ボリューム (MSB) はありましたが、 2バイト目が 39 の チャンネル・ボリューム (LSB) はありません。

2バイト目の値の割り振り方からして、もともと、type=7 だけでボリュームの指定をしていたんでしょうね。

もっと精度がほしくて、後になってtype=39 で、さらに細かく指定できるように、追加されたのでしょう。

それほど細かく指定しなくてもよければ、上位側だけ設定すれば十分という事ですね

ターゲットのSMFでは使われてませんが、とりあえず type=39 も対応しておきます。

ここで初めて、鍵盤オン・オフ以外のイベントへの対応となります。

これまでは、鍵盤オン・オフのイベントを処理する関数、note_onoff()の冒頭で、 イベント時間に至るまでの分のSIN波形の生成処理をしてきました。

鍵盤オン・オフのイベントだけが、波形出力のトリガとなっていたので、これで問題ありませんでした。

これからは、チャンネル・ボリュームのイベントの際も、イベント時刻までの波形を出力してから、 ボリュームの設定をせねばなりません。

なので、note_onoff()関数の冒頭の波形出力処理を分離して、 鍵盤オン・オフのイベントに限らず、全てのイベント発生で、波形出力の関数を呼び出すようにします。

まずは一旦、波形出力処理を別関数へ移すだけの変更を試してみます

プログラム

prog_onoff_sin12.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, low, note, velo);
			break;
		case 0xa:
		case 0xb:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff_sin12 prog_onoff_sin12.c -lm

$ cat L3007_06.MID | ./prog_onoff_sin12 -r 44100 -b 16 -s -c 2 -play

prog_onoff_sin12_xaa.mp3

prog_onoff_sin12_xab.mp3

prog_onoff_sin12_xac.mp3

とくに問題なく動作しますね。

データの違いを確認してみます

$ cat L3007_06.MID | ./prog_onoff_sin11 -r 44100 -b 16 -s -c 2 > prog_onoff_sin11.raw
$ cat L3007_06.MID | ./prog_onoff_sin12 -r 44100 -b 16 -s -c 2 > prog_onoff_sin12.raw

$ cmp prog_onoff_sin11.raw prog_onoff_sin12.raw
cmp: EOF on prog_onoff_sin11.raw
$

prog_onoff_sin11 の方のデータの EOF までは一致 (?)

ファイルサイズが、62815860 から 63504000 に増えてました。

なるほど、これまでは最後の鍵盤オフ・イベントまでしか波形出力が無かったのですが、 そのあとの何らかのイベントが存在して、そこまで残響音と無音のデータが追加されてるようです

さて元に戻って、上記の対応をした上で、チャンネル・ボリュームに対応してみます。

鍵盤オン・オフ以外のイベントにも対応したので、プログラムの名前を変えておきます

プログラム

prog1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = note_buf[i].ch / 15.0;
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++) ch_vol[i] = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog1 prog1.c -lm

$ cat L3007_06.MID | ./prog1 -r 44100 -b 16 -s -c 2 -play

prog1_xaa.mp3

prog1_xab.mp3

全体的に音が小さくなりました。

これまでは、全チャンネルのボリューム全開だったわけですから、絞る方向になります。

先の type=7 のデータ的には、ch=1,3,9 あたりが、ボリューム大きめで、ch=6,7,8 あたりが絞られてます。

左右の割り振りは、左から右へと ch=0〜15 まで並べているので、 左側の音が大きめ、右側の音が小さめのバランスに変わりました


PAN

左右のバランスが変わったところで、次は、Panpot を正しく設定してみます。

これまでは、出力がステレオの場合、MIDIチャンネル 0 から 15 を、左から右へ、均等に並べていました。

左右の割り振りの設定も、コントロール・チェンジ・イベントにあります

コントロール・チェンジ・イベント

この、2バイト目のタイプの値が、

となります。

それぞれ 3バイト目に、7ビットの 0 から 127 の値が指定されます。

MSB は上位の 7ビット分、LSB は下位の 7ビット分、合計 14ビットの値となります

先のMIDIチャンネル・ボリュームの場合と、だいたい同じですね。

Panpot の 14ビットの値は、0 で最も左、16383 (2^14-1) で、最も右です

まずターゲットのSMFに、このイベントが含まれているか見ておきます。

例によって、

prog_onoff6.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

で覗いてみます

$ gcc -o prog_onoff6 prog_onoff6.c

$ cat L3007_06.MID | ./prog_onoff6 | grep 'ctl chg' | grep 'type=10 '
ctl chg ch=0 type=10 v=44
ctl chg ch=1 type=10 v=64
ctl chg ch=2 type=10 v=44
ctl chg ch=3 type=10 v=74
ctl chg ch=4 type=10 v=44
ctl chg ch=5 type=10 v=44
ctl chg ch=6 type=10 v=50
ctl chg ch=7 type=10 v=74
ctl chg ch=8 type=10 v=50
ctl chg ch=9 type=10 v=64
ctl chg ch=10 type=10 v=64
ctl chg ch=11 type=10 v=50
ctl chg ch=4 type=10 v=80
ctl chg ch=0 type=10 v=80
ctl chg ch=8 type=10 v=64
$

$ cat L3007_06.MID | ./prog_onoff6 | grep 'ctl chg' | grep 'type=42 '
$

これまた、MIDIチャンネル・ボリュームと同じで、MSBのイベントだけがあります。

同様に、精度を向上のため、LSBは後に追加されたのでしょう

ボリュームと違って、初期値は中央の位置にすべきでしょうか。

14ビットなので、2つに分けると

左側: 0 から 8191 (8191-0+1 = 8192)
右側: 8192 から 16383 (16383-8192+1 = 8192)

うーむ。表現できる値は偶数個あるので、丁度中央の値は無いですね

逆に、14ビットの値で、左右のバランスを割り振る処理から考えてみましょう。

現状では、prog1.c の data_out() 関数の次の処理です

}else{
	pan = note_buf[i].ch / 15.0;
	vl += (1 - pan) * v;
	vr += pan * v;
}

変数 pan の値が 0.0 のとき、左最大、 1.0 のとき、右最大になってます。

ここの 1行目を

pan = 14ビットの値 / 16383.0

とすれば、pan の値は 0.0 から 1.0 になって、よさげです。

となると pan が 0.5 となる値を逆算すると

0.5 * 16383.0 = 8191.5

うー。丁度中央の値には設定できませんね。

確かにそうなのですが、右最大の設定をあきらめれば、どうでしょうか

左側: 0 から 8191 (8191-0+1 = 8192)
中央: 8192
右側: 8193 から 16384 (16384-8193+1 = 8192)

と考えてみます。

14ビットの値は、0 から 16383 まで。

なので、右最大の 16384 は表現できずに設定できません。

しかしこの考えにすれば、先の1行目は

pan = 14ビットの値 / 16384.0

となり、中央の値は確かに 0.5 * 16384.0 = 8192

まぁ、右最大で 1/16384 程度ズレますが、 中央の設定が、中央に定まらないよりは、いいのかもしれません

プログラム

prog2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog2 prog2.c -lm

$ cat L3007_06.MID | ./prog2 -r 44100 -b 16 -s -c 2 -play

prog2_xaa.mp3

prog2_xab.mp3

これで左右のバランスが、よりしっくりきました。(本当だろうか?)


テンポ

曲のテンポを設定するイベントがありましたが、ここまで無視してきました。

とりあえずの「四分音符一つあたり0.5秒固定」のままです。

ここらで、ちゃんと設定してみましょう

復習すると、イベントの発行時間を、曲の先頭からの時刻(秒)にするには

デルタタイムの合計 ÷ ヘッダの分解能 × 四分音符一つあたりの秒数

ここで、四分音符一つあたりの秒数 を 0.5 固定としてきました

ここまでのプログラム

prog2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = (double)delta_sum / div4 * 0.5;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf:
				n = rd();
				for(i=0; i<n; i++) rd();
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

該当の処理をしてる箇所は、main() 関数の while() ループの冒頭で

while((v = rd_delta()) != EOF){
	delta_sum += v;
	sec = (double)delta_sum / div4 * 0.5;

まさに 0.5 固定です

この 四分音符一つあたりの秒数 を設定するメタ・イベントが、セット・テンポでした

メタイベントとは

セット・テンポ

3バイトの整数はマイクロ秒なので、秒に換算するには 10^6 で割ります。

簡単そうですね。

3バイトの整数の値を 10^6 で割算して、処理箇所の 0.5 の変わりに使うだけ?

そうはいきません。

後回しにしてた理由は、 実際には曲の途中のメタイベントで、どんどんテンポが変更される事もあり、 複雑な計算になる場合もある、という事でした

例えば、四分音符一つあたり 0.5 秒の設定で、10秒経過したとします。

この間は

デルタタイムの合計 ÷ ヘッダの分解能 × 0.5

の値が 0 から 10 まで変化するので

デルタタイムの合計 ÷ ヘッダの分解能

の値は 0 から 20 まで変化してます

ここで 四分音符一つあたり 0.1 秒 に設定するイベントが来たとして、 単純に 0.5 を 0.1 に変更して、式にあてはめてしまうと...

デルタタイムの合計 ÷ ヘッダの分解能

の値は 20 になっているので

デルタタイムの合計 ÷ ヘッダの分解能 × 0.1

の値は 2

曲の先頭から、10秒経過したはずが、2秒の位置に戻ってしまいます Σ(ToT)

正しく処理するには、テンポを設定した時の時刻を記録しておいて、 その時点からの 「デルタタイムの合計」 で計算し、 テンポを設定してからの経過秒で考えねばなりません

これで大丈夫でしょう

ターゲットの SMF のメタ・イベントを調べてみましょう。

例によって、

prog_onoff6.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ...\n", v);
			for(i=0; i<v; i++) c = getchar(); /* skip */
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

メタ・イベントのタイプ 0x51 (10進数では 81) なので

$ gcc -o prog_onoff6 prog_onoff6.c

$ cat L3007_06.MID | ./prog_onoff6 | grep 'meta' | grep 'type=81 '
meta event type=81 len=3 ...
meta event type=81 len=3 ...
meta event type=81 len=3 ...
meta event type=81 len=3 ...
meta event type=81 len=3 ...
meta event type=81 len=3 ...
$

複数回テンポの設定がありますが、肝心の3バイトの整数を表示してませんでした(>_<)

if(c1 == 0xff){
	printf("meta event type=%d ", c2);
	v = getchar();
	printf("len=%d ...\n", v);
	for(i=0; i<v; i++) c = getchar(); /* skip */
	continue;
}

ここだけ、変更しておきます。

プログラム

prog_onoff7.c
#include <stdio.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		printf("delta time=%d\n", v);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ", v);
			for(i=0; i<v; i++){
				c = getchar(); /* skip */
				if(i < 16) printf("0x%02x ", c);
			}
			printf("\n");
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

if(c1 == 0xff){
	printf("meta event type=%d ", c2);
	v = getchar();
	printf("len=%d ", v);
	for(i=0; i<v; i++){
		c = getchar(); /* skip */
		if(i < 16) printf("0x%02x ", c);
	}
	printf("\n");
	continue;
}

これで、16バイトまでは表示します

$ gcc -o prog_onoff7 prog_onoff7.c

$ cat L3007_06.MID | ./prog_onoff7 | grep meta | grep type=81
meta event type=81 len=3 0x07 0x81 0x1b
meta event type=81 len=3 0x07 0xe4 0x7d
meta event type=81 len=3 0x08 0xe3 0x7d
meta event type=81 len=3 0x0a 0x85 0xf8
meta event type=81 len=3 0x0e 0x15 0xc5
meta event type=81 len=3 0x0b 0x71 0xb0
$

3バイトの整数は、ビッグ・エンディアンなので

$ gdb
  :
(gdb) p (0x07<<16)+(0x81<<8)+0x1b
$1 = 491803
(gdb) p (0x07<<16)+(0xe4<<8)+0x7d
$3 = 517245
(gdb) p (0x08<<16)+(0xe3<<8)+0x7d
$4 = 582525
(gdb) p (0x0a<<16)+(0x85<<8)+0xf8
$5 = 689656
(gdb) p (0x0e<<16)+(0x15<<8)+0xc5
$6 = 923077
(gdb) p (0x0b<<16)+(0x71<<8)+0xb0
$7 = 750000
(gdb) quit
$

値の単位はマイクロ秒なので、最初は四分音符一つあたり 0.5 秒程度のようです。

0.9秒 程度にして、遅くしてる箇所もあるようですね。

さっそく反映してみましょう

テンポの初期値は、これまで通り四分音符一つあたり 0.5 秒の設定にしておいて、 セット・テンポのメタ・イベントがきたら設定を変更します

プログラム

prog3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog3 prog3.c -lm

$ cat L3007_06.MID | ./prog3 -r 44100 -b 16 -s -c 2 -play

prog3_xaa.mp3

prog3_xab.mp3

play コマンドの表示する時間を見ると、最後の表示が

00:06:01.52

変更前の prog2 では

00:06:00.00

あまり変わってませんね (^_^;)

どのあたりでテンポを変更してるのか、見てみましょう。

イベントの表示を見てきたプログラムを改造して、 イベントの時刻を表示するようにしてみます。

デルタタイムの積算と、テンポの設定処理を、 イベントの表示を見てきたプログラムに反映すれば、出来そうですね。

プログラム

prog_onoff8.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];
	int div4, delta_sum;
	double sec;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);
	div4 = v;


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	delta_sum = 0;
	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;
		printf("sec=%.3f ", sec);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ", v);

			if(c2 == 0x51){ /* set tempo */
				tempo = 0;
			}
			for(i=0; i<v; i++){
				c = getchar(); /* skip */
				if(i < 16) printf("0x%02x ", c);
				if(c2 == 0x51){ /* set tempo */
					tempo <<= 8;
					tempo |= c;
				}
			}
			printf("\n");
			if(c2 == 0x51){ /* set tempo */
				tempo_delta_sum = delta_sum;
				tempo_sec = sec;
			}
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

解説

$ gcc -o prog_onoff8 prog_onoff8.c -lm

$ cat L3007_06.MID | ./prog_onoff8 | grep meta | grep type=81
sec=0.000 meta event type=81 len=3 0x07 0x81 0x1b
sec=314.262 meta event type=81 len=3 0x07 0xe4 0x7d
sec=343.745 meta event type=81 len=3 0x08 0xe3 0x7d
sec=344.910 meta event type=81 len=3 0x0a 0x85 0xf8
sec=345.600 meta event type=81 len=3 0x0e 0x15 0xc5
sec=346.523 meta event type=81 len=3 0x0b 0x71 0xb0
$

テンポの変更が入るのは、5分を超えた曲の終盤ばかりですね。

3 バイトの整数の値 (単位マイクロ秒) は

491803
517245
582525
689656
923077
750000

でした。

四分音符一つ 0.9 秒程度に設定してる期間が、0.9 秒程度。

つまり一拍分だけです。

エンディングで、フェルマータで引っ張ってるようですね


ピッチ・ベンド

ああ、ややこしや

「とりあえずスキップ」してるイベントに、pitch wheel change イベントがありました。

いわゆるギターのチョーキングみたいなヤツですね

このイベントそのものは、14ビットの整数が来るだけですが、対応するのは結構ややこしいです

ややこしさを感じる最初の違和感として、 ここまでパラメータはビッグ・エンディアンばかりなのに、 このイベントは、下位、上位の順でデータが並び、リトル・エンディアンになってます

さらに 14ビットの値を、符号なしの整数として扱うのか、符号ありの整数として扱うのか ?

直感的に 14ビットの値が 0 なら、周波数の変化なしに思えますが、 符号なしで扱うなら、正方向にしか変化出来ないのは困ります。

中ほどの値で変化なしとするのか?

参考URLなどをみてみると

----
  :
「MSB:LSBを連結し14ビットの値(-8192から+8191)として扱う」
  :
----
  :
名称
  ピッチベンド

メッセージ配列
  {0xEn, 0xMM, 0xLL}
  n=チャンネル番号(0〜F)
  MM=値(00〜7F)
  LL=値(00〜7F)

解説
    :
  最低値は{0xEn, 0x00, 0x00}, 中央値は{0xEn, 0x40, 0x00}, 最高値は{0xEn, 0x7F, 0x7F}である
    :
----
  :
ピッチベンドが -8192,0,8191 に対応する値llmmはそれぞれ 0x0000,0x4000,0x7F7F となる
  :
----

などの情報がありました。(メッセージ配列の、エンディアンが間違ってる?)

これらを総合すると

となると、結局は、次の解釈でいいはずです

そして、目一杯変化させたとき、実際の音程はどれだけ変化させるべきか?

当時のシンセサイザの機種によって、バラバラだったせいでしょうか。

pitch wheel change とは、別のイベントで設定するようになってます。

システム・エクスクルーシブ・メッセージの RPN なるものを使って設定します。

この RPN が、何やらややこしいです。

仕組みの詳細は、参考URLの

http://www.izmi.jp/sol/glossary/midi/rpnnrpn.html

にお任せします (^_^;

そして次は、規格ではなくこちら側の問題なのですが、

今のSIN波形の生成の処理では、音の途中で簡単に周波数を変えれません。

プログラム

prog3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			v = sin(2 * M_PI * freq * dsec);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

data_out() 関数の該当箇所

for(i=0; i<NOTE_BUF_N; i++){
	if(note_buf_is_free(i)) continue;
	freq = note_to_freq(note_buf[i].note);
	dsec = sec - note_buf[i].on_sec;
	v = sin(2 * M_PI * freq * dsec);

ぱっと見、変数 freq を変化させればいいように思えますが、実はそれではうまくいきません。

この処理は、鍵盤オンからずっと freq が一定の値であることが、前提になってます

例えば、freq = 1000 Hz で dsec = 1/1000 秒経過したとします

cycle = freq * dsec = 1000 * (1/1000) = 1 サイクル

SIN波がちょうど 1 サイクル分出力されて、v の値は 0, 1, 0, -1, 0 と変化して 0 になってます

ここで、freq を 750 Hz に変更したとすると、sin()関数の出力は、 鍵盤オンから freq = 750 Hz の設定で、dsec = 1/1000 秒経過したときの値と同じになります

cycle = freq * dsec = 750 * (1/1000) = 0.75 サイクル
sin( 2π × 0.75 ) -1

値 0 から、いきなり -1 への不連続な変化になります。

まずは、この問題から片付けていきましょう

SIN波形生成処理

周波数を刻々と変化させるには、音を鳴らし出してからの経過時間だけでは情報が足りません。

最後にデータ出力した時のサイクルを記録するようにしてみます

ここでのサイクルとは、音が鳴り始めてから、 SINパターンを何回分繰り返したかを表す、小数の回数とします。

周波数が一定ならば

サイクル = 音の周波数 × 音の鳴ってる時間

になります

最後にデータ出力した時のサイクルとあわせて、その時の時刻も記録するようにします

サンプリング周波数は一定なので、最後の出力時刻は常に現在時刻から、 サンプリング周期 (1 / サンプリング周波数) 分を引けば済みます。

なので、わざわざ記録しなくてもなんとかなりそうなのですが、 pitch wheel change イベントの時刻は、点在して並ぶサンプリングのタイミングと、 一致するとは限りません。この補正のために、時刻も記録しておきます。

もちろん、ピッチに変化を与えるタイミングの時刻が多少前後しても、 大勢に影響は無いのですが、一応厳密にしておきましょう

どういう事なのか、もう少し説明してみます

smp_cnt   100  101  102  103  104  105
           |    |    |    |    |    |
    ... -------------------------------- ...
             |               |
            key_on         pitch

   smp_cnt = サンプリング回数
   key_on  = 鍵盤オン・イベント
   pitch   = pitch wheel change イベント

例えばサンプリング周波数8000 Hzで、鍵盤オン・イベントがきて1000 Hzの音を鳴らすとします。

サンプリング時刻は曲の先頭から、0, 1/8000, 2/8000 ... と進行して、100回目なら時刻 100/8000(秒)です

鍵盤オン・イベントの時刻はSMFの内容次第なので、サンプリング周波数とは無関係です。

イベントのデルタタイムの合計や、テンポの設定からイベントの時刻を算出しています。

そして、pitch wheel change イベントの時刻も同様です

smp_cnt 100 と 101 の間のどこかの時刻で鍵盤オンになって、音が鳴りだしたとすると、 smp_cnt 100 の時点では音は鳴ってなくて、101 の時点では鳴ってるはずです。

その 101 の時のSIN波形は? 鍵盤オンから 101 の間の経過時間で決まります。 鍵盤オンからの経過時間は、101/8000 - <鍵盤オン・イベントの時刻>。

音の高さは 1000 Hz なので、その間に進行した波形の個数、つまりサイクルは

サイクル(回数) = 音の周波数(回数/秒) × 音の経過時間(秒)
サイクル = 1000 * ( 101/8000 - 鍵盤オン・イベントの時刻 )
SIN波形の値 = SIN( 2π × サイクル )

102 の時の波形は、101の時のサイクルが記録済みとしたら

102のサイクル = 101のサイクル + 101から102の間に進行したサイクル分
101から102の間に進行したサイクル分 = 音の周波数 × 101から102の経過時間

経過時間は smp_freq 8000 Hz なので、1/8000(秒)
102のサイクル = 101のサイクル + 1000 * 1/8000

波形の値は、その時点のサイクルの値さえ分かれば

SIN波形の値 = SIN( 2π × サイクル )

そして、最後に出力した時のサイクルと時刻を記録します

103 の時も同様です

103のサイクル = 102のサイクル + 1000 * 1/8000
SIN波形の値 = SIN( 2π × サイクル )

ここまでは、最後の出力時刻を使ってません

そして、103 と 104 の間のどこかで pitch wheel changeイベントが発生し、 音の周波数が、1000 Hz から 1100 Hz に変化するとします。

       103           104
        |             |
... ------------------------ ...
               |
    1000 Hz -->|<-- 1100 Hz --
               |
             pitch

103 と 104 の間のとこかで、音の高さが1000 Hz から 1100 Hz に変わります。

この変わり目で、SIN波形の値を連続にせねばなりません。

pitch の時点のサイクルは

pitchの時点のサイクル = 103のサイクル + 音の周波数 × 103からpitchまでの経過時間

103からpitchまでの経過時間 = (pitch wheel changeイベントの時刻) - 103の時刻

SIN波形の値 = SIN( 2π × サイクル )
なので
pitchの時点のSIN波形 = SIN( 2π × pitchの時点のサイクル )

ここで 103の時刻 として、最後の出力時刻を使います。

実際には出力しませんが、最後に出力したサイクルの記録と時刻を、 pitch の時点のものに更新しておきます

104 の時は、pitch の時点から音の高さは 1100 Hz 一定なので

104のサイクル = pitchの時点のサイクル + 音の周波数 × pitchから104 までの経過時間
104のサイクル = pitchの時点のサイクル + 1100 × ( 104の時刻 - pitchの時刻 )

pitchの時刻 は、最後の出力時刻として記録されていて、 104の時刻 は 104/8000 です

まぁ厳密にしなくても、 pitch のイベントの時刻を、少し手前のちょうど 103 の時点で発生した 事にしてしまえば、最後の出力時刻を記録しなくても済む訳です

では、最後の出力サイクルと時刻を記録する方式で、プログラムを変更してみます

プログラム

prog4.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			note_buf[i].cycle += freq * (sec - note_buf[i].sec);
			note_buf[i].sec = sec;
			v = sin(2 * M_PI * note_buf[i].cycle);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog4 prog4.c -lm

$ cat L3007_06.MID | ./prog4 -r 44100 -b 16 -s -c 2 -play

prog4_xaa.mp3

prog4_xab.mp3

波形生成処理をいじりましたが、以前と同様に聴こえますね

$ cat L3007_06.MID | ./prog3 -r 44100 -b 16 -s -c 2 > prog3.raw
$ cat L3007_06.MID | ./prog4 -r 44100 -b 16 -s -c 2 > prog4.raw
$ cmp prog3.raw prog4.raw
prog3.raw prog4.raw differ: char 1552273, line 1079
$

同じように聴こえますが、データ的には多少違う値に変わってるようです。

63Mバイト程度のファイルサイズで、オフセット1.5Mバイトの位置で最初の違いがでてます。

頭の2Mバイト程度をダンプして比較してみます

$ head -c 2000000 prog3.raw | hd > prog3.raw.hd
$ head -c 2000000 prog4.raw | hd > prog4.raw.hd
$ diff -c prog3.raw.hd prog4.raw.hd
*** prog3.raw.hd        Tue Aug 20 12:02:11 2013
--- prog4.raw.hd        Tue Aug 20 12:02:20 2013
***************
*** 53527,53533 ****
  0017af60  68 01 bc 00 63 01 ba 00  5c 01 b6 00 54 01 b2 00  |h...c...\...T...|
  0017af70  49 01 ac 00 3d 01 a6 00  2e 01 9e 00 1f 01 96 00  |I...=...........|
  0017af80  0d 01 8d 00 fa 00 83 00  e6 00 78 00 d0 00 6d 00  |..........x...m.|
! 0017af90  b9 00 61 00 a2 00 54 00  89 00 48 00 70 00 3a 00  |..a...T...H.p.:.|
  0017afa0  55 00 2d 00 3b 00 1f 00  20 00 11 00 05 00 02 00  |U.-.;... .......|
  0017afb0  eb ff f5 ff d0 ff e7 ff  b5 ff d9 ff 9b ff cb ff  |................|
  0017afc0  81 ff be ff 68 ff b1 ff  50 ff a4 ff 39 ff 98 ff  |....h...P...9...|
--- 53527,53533 ----
  0017af60  68 01 bc 00 63 01 ba 00  5c 01 b6 00 54 01 b2 00  |h...c...\...T...|
  0017af70  49 01 ac 00 3d 01 a6 00  2e 01 9e 00 1f 01 96 00  |I...=...........|
  0017af80  0d 01 8d 00 fa 00 83 00  e6 00 78 00 d0 00 6d 00  |..........x...m.|
! 0017af90  ba 00 61 00 a2 00 54 00  89 00 48 00 70 00 3a 00  |..a...T...H.p.:.|
  0017afa0  55 00 2d 00 3b 00 1f 00  20 00 11 00 05 00 02 00  |U.-.;... .......|
  0017afb0  eb ff f5 ff d0 ff e7 ff  b5 ff d9 ff 9b ff cb ff  |................|
  0017afc0  81 ff be ff 68 ff b1 ff  50 ff a4 ff 39 ff 98 ff  |....h...P...9...|
***************
*** 71115,71121 ****
  001bfaa0  0d 00 07 00 28 00 15 00  43 00 23 00 5e 00 31 00  |....(...C.#.^.1.|
  001bfab0  78 00 3e 00 91 00 4c 00  a9 00 58 00 c1 00 65 00  |x.>...L...X...e.|
  001bfac0  d7 00 70 00 ec 00 7c 00  00 01 86 00 13 01 90 00  |..p...|.........|
! 001bfad0  24 01 99 00 33 01 a1 00  41 01 a8 00 4d 01 ae 00  |$...3...A...M...|
  001bfae0  56 01 b3 00 5f 01 b7 00  65 01 bb 00 69 01 bd 00  |V..._...e...i...|
  001bfaf0  6b 01 be 00 6b 01 be 00  69 01 bd 00 65 01 bb 00  |k...k...i...e...|
  001bfb00  5f 01 b8 00 58 01 b4 00  4e 01 af 00 42 01 a9 00  |_...X...N...B...|
--- 71115,71121 ----
  001bfaa0  0d 00 07 00 28 00 15 00  43 00 23 00 5e 00 31 00  |....(...C.#.^.1.|
  001bfab0  78 00 3e 00 91 00 4c 00  a9 00 58 00 c1 00 65 00  |x.>...L...X...e.|
  001bfac0  d7 00 70 00 ec 00 7c 00  00 01 86 00 13 01 90 00  |..p...|.........|
! 001bfad0  24 01 99 00 33 01 a1 00  41 01 a8 00 4c 01 ae 00  |$...3...A...L...|
  001bfae0  56 01 b3 00 5f 01 b7 00  65 01 bb 00 69 01 bd 00  |V..._...e...i...|
  001bfaf0  6b 01 be 00 6b 01 be 00  69 01 bd 00 65 01 bb 00  |k...k...i...e...|
  001bfb00  5f 01 b8 00 58 01 b4 00  4e 01 af 00 42 01 a9 00  |_...X...N...B...|
   :
  (略)
cmp コマンドの結果から、1552273 バイト目は、オフセット1552272

$ gdb
  :
(gdb) p /x 1552272
$1 = 0x17af90
$ quit

ほぼ同じなのですが、確かにオフセット 0x17a90の 結果が 0xb9 から0xba に変わってます。

他の変わってる箇所も、偶数バイトで値が1だけ変わっているようなので、16ビット出力でLSBが1だけ変わってる程度です。

所々で振幅が 1 / 65536 程度変わっても、まぁだいたい同じように聴こえるわけです

ビブラート

波形生成処理をピッチ・ベンド対応に備えて修正してみましたが、ちゃんと音の高さが変わるでしょうか。

本格的にピッチ・ベンド対応する前に、ちょっとビブラートでもかけて確認しておきましょう

プログラム

prog4.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			note_buf[i].cycle += freq * (sec - note_buf[i].sec);
			note_buf[i].sec = sec;
			v = sin(2 * M_PI * note_buf[i].cycle);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			note_buf[i].cycle += freq * (sec - note_buf[i].sec);
			note_buf[i].sec = sec;
			v = sin(2 * M_PI * note_buf[i].cycle);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

の data_out() 関数

for(i=0; i>NOTE_BUF_N; i++){
	if(note_buf_is_free(i)) continue;
	freq = note_to_freq(note_buf[i].note);
	note_buf[i].cycle += freq * (sec - note_buf[i].sec);
	note_buf[i].sec = sec;
	v = sin(2 * M_PI * note_buf[i].cycle);

この freq に揺さぶりをかけて試してみます

6 Hz のサイン波は、SIN( 2π × 6 × 経過時間 ) で、+1.0から-1.0の値をとります

鍵盤の半音分音が高くなると、周波数は 2^(1/12) 倍になりますから、 元の周波数から、2^(1/(12*4)) 倍と、2^(-1/(12*4)) 倍の間で変化させればいいですね。

上記の freq 設定箇所の後に、二行追加です

  :
freq = note_to_freq(note_buf[i].note);
dsec = sec - note_buf[i].on_sec;
if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
  :

プログラム

prog5.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			freq = note_to_freq(note_buf[i].note);
			dsec = sec - note_buf[i].on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			note_buf[i].cycle += freq * (sec - note_buf[i].sec);
			note_buf[i].sec = sec;
			v = sin(2 * M_PI * note_buf[i].cycle);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[ note_buf[i].ch ] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[ note_buf[i].ch ] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			}
			break;
		case 0xa:
		case 0xe:
			rd();
			rd();
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog5 prog5.c -lm

$ cat L3007_06.MID | ./prog5 -r 44100 -b 16 -s -c 2 -play

prog5_xaa.mp3

prog5_xab.mp3

たしかに揺れてます(((~_~)))

イベント表示

ピッチ・ベンド関連のイベントを表示してみます。

例によって

プログラム

prog_onoff8.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];
	int div4, delta_sum;
	double sec;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);
	div4 = v;


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	delta_sum = 0;
	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;
		printf("sec=%.3f ", sec);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ", v);

			if(c2 == 0x51){ /* set tempo */
				tempo = 0;
			}
			for(i=0; i<v; i++){
				c = getchar(); /* skip */
				if(i < 16) printf("0x%02x ", c);
				if(c2 == 0x51){ /* set tempo */
					tempo <<= 8;
					tempo |= c;
				}
			}
			printf("\n");
			if(c2 == 0x51){ /* set tempo */
				tempo_delta_sum = delta_sum;
				tempo_sec = sec;
			}
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */
#include <stdio.h>
#include <math.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];
	int div4, delta_sum;
	double sec;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);
	div4 = v;


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	delta_sum = 0;
	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;
		printf("sec=%.3f ", sec);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ", v);

			if(c2 == 0x51){ /* set tempo */
				tempo = 0;
			}
			for(i=0; i<v; i++){
				c = getchar(); /* skip */
				if(i < 16) printf("0x%02x ", c);
				if(c2 == 0x51){ /* set tempo */
					tempo <<= 8;
					tempo |= c;
				}
			}
			printf("\n");
			if(c2 == 0x51){ /* set tempo */
				tempo_delta_sum = delta_sum;
				tempo_sec = sec;
			}
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

$ gcc -o prog_onoff8 prog_onoff8.c -lm

まず pitch wheel chage イベントそのものから

$ cat L3007_06.MID | ./prog_onoff8 | grep pitch
sec=34.426 pitch wheel change ch=11 lsb=0 msb=0
sec=34.467 pitch wheel change ch=11 lsb=0 msb=3
sec=34.508 pitch wheel change ch=11 lsb=0 msb=7
sec=34.549 pitch wheel change ch=11 lsb=0 msb=10
sec=34.590 pitch wheel change ch=11 lsb=0 msb=14
sec=34.631 pitch wheel change ch=11 lsb=0 msb=17
sec=34.672 pitch wheel change ch=11 lsb=0 msb=21
sec=34.713 pitch wheel change ch=11 lsb=0 msb=24
sec=34.754 pitch wheel change ch=11 lsb=0 msb=28
sec=34.795 pitch wheel change ch=11 lsb=0 msb=31
  :
$

イベントのMIDIチャンネルをみてみると

$ cat L3007_06.MID | ./prog_onoff8 | grep pitch | sed -e 's/.*\(ch=[0-9]*\).*/\1/' | sort | uniq
ch=1
ch=11
ch=2
ch=4
ch=5
ch=6
$

なにやら lsb=0

全部そうでしょうか?

$ cat L3007_06.MID | ./prog_onoff8 | grep pitch | sed -e 's/.*\(lsb=[0-9]*\).*/\1/' | sort | uniq
lsb=0
lsb=42
lsb=43
lsb=86
lsb=92
$

そうでもないようです

$ cat L3007_06.MID | ./prog_onoff8 | grep pitch | sed -e '/lsb=0 /d'
sec=75.574 pitch wheel change ch=4 lsb=86 msb=74
sec=77.541 pitch wheel change ch=4 lsb=86 msb=74
sec=79.754 pitch wheel change ch=4 lsb=86 msb=74
sec=83.443 pitch wheel change ch=4 lsb=86 msb=74
sec=85.410 pitch wheel change ch=4 lsb=86 msb=74
sec=87.869 pitch wheel change ch=4 lsb=86 msb=74
sec=91.803 pitch wheel change ch=4 lsb=86 msb=74
sec=94.057 pitch wheel change ch=4 lsb=43 msb=69
sec=148.361 pitch wheel change ch=4 lsb=86 msb=74
sec=150.328 pitch wheel change ch=4 lsb=86 msb=74
sec=152.541 pitch wheel change ch=4 lsb=86 msb=74
sec=156.229 pitch wheel change ch=4 lsb=86 msb=74
sec=158.197 pitch wheel change ch=4 lsb=86 msb=74
sec=160.656 pitch wheel change ch=4 lsb=86 msb=74
sec=164.590 pitch wheel change ch=4 lsb=86 msb=74
sec=166.844 pitch wheel change ch=4 lsb=92 msb=80
sec=168.524 pitch wheel change ch=4 lsb=86 msb=74
sec=211.598 pitch wheel change ch=1 lsb=43 msb=69
sec=283.647 pitch wheel change ch=4 lsb=42 msb=53
sec=285.615 pitch wheel change ch=4 lsb=42 msb=53
sec=287.582 pitch wheel change ch=4 lsb=42 msb=53
sec=289.549 pitch wheel change ch=4 lsb=42 msb=53
sec=291.516 pitch wheel change ch=4 lsb=42 msb=53
sec=293.483 pitch wheel change ch=4 lsb=42 msb=53
$
$ cat L3007_06.MID | ./prog_onoff8 | grep pitch | sed -e '/lsb=0 /d' | wc -l
24
$ cat L3007_06.MID | ./prog_onoff8 | grep pitch | wc -l
816
$

でも、ほとんどが lsb=0 ですね

次に RPN まわりをみてみましょう。

詳細は、参考URL

http://www.izmi.jp/sol/glossary/midi/rpnnrpn.html

にお任せするとして...(^_^;)

関連するイベントはコントロール・チェンジです

コントロール・チェンジとは

関連のコントロール・チェンジのタイプは

あと、もしかしたら

も関連するかも?

ターゲットのSMFで使ってるかみてみます

$ cat L3007_06.MID | ./prog_onoff8 | grep ctl | sed -e 's/.*\(type=[0-9]*\) .*/\1/' | sort | uniq
type=0
type=1
type=10
type=100
type=101
type=11
type=32
type=6
type=7
type=91
type=93
type=98
type=99
$

type が 100,101,6 は使ってますが、36,96,97 は使われてませんでした。

なので、気にしないでおきます :-p)

では、このコントロール・チェンジのタイプ 100, 101, 6 についてです。

参考URLによると、タイプ 100, 101 で 0 が指定されてる状態のときに、 タイプ 6 で、最大の音程変化量の指定となります。

音程変化量は鍵盤の半音の数で指定されます(1なら半音、2なら全音、12なら1オクターブ)

イベントを表示してみてみましょう

$ cat L3007_06.MID | ./prog_onoff8 | grep ctl | grep -e 'type=100 ' -e 'type=101 ' -e 'type=6 '
sec=0.994 ctl chg ch=0 type=6 v=40
sec=1.009 ctl chg ch=0 type=6 v=114
sec=1.025 ctl chg ch=0 type=6 v=50
sec=1.040 ctl chg ch=0 type=6 v=34
sec=1.045 ctl chg ch=0 type=101 v=127
sec=1.050 ctl chg ch=0 type=100 v=127
sec=1.055 ctl chg ch=1 type=101 v=0
sec=1.060 ctl chg ch=1 type=100 v=0
sec=1.066 ctl chg ch=1 type=6 v=12
sec=1.081 ctl chg ch=1 type=6 v=34
sec=1.096 ctl chg ch=1 type=6 v=14
sec=1.112 ctl chg ch=1 type=6 v=54
sec=1.127 ctl chg ch=1 type=6 v=114
sec=1.142 ctl chg ch=1 type=6 v=62
sec=1.148 ctl chg ch=1 type=101 v=127
sec=1.153 ctl chg ch=1 type=100 v=127
sec=1.168 ctl chg ch=2 type=6 v=44
sec=1.183 ctl chg ch=2 type=6 v=114
sec=1.199 ctl chg ch=2 type=6 v=44
sec=1.204 ctl chg ch=2 type=101 v=0
sec=1.209 ctl chg ch=2 type=100 v=0
sec=1.214 ctl chg ch=2 type=6 v=12
sec=1.219 ctl chg ch=2 type=101 v=127
sec=1.224 ctl chg ch=2 type=100 v=127
sec=1.240 ctl chg ch=3 type=6 v=59
sec=1.255 ctl chg ch=3 type=6 v=26
sec=1.270 ctl chg ch=3 type=6 v=65
sec=1.286 ctl chg ch=3 type=6 v=74
sec=1.301 ctl chg ch=3 type=6 v=52
sec=1.317 ctl chg ch=3 type=6 v=68
sec=1.322 ctl chg ch=3 type=101 v=127
sec=1.327 ctl chg ch=3 type=100 v=127
sec=1.332 ctl chg ch=4 type=101 v=0
sec=1.337 ctl chg ch=4 type=100 v=0
sec=1.342 ctl chg ch=4 type=6 v=12
  :
sec=2.003 ctl chg ch=11 type=101 v=0
sec=2.008 ctl chg ch=11 type=100 v=0
sec=2.013 ctl chg ch=11 type=6 v=12
sec=2.018 ctl chg ch=11 type=101 v=127
sec=2.024 ctl chg ch=11 type=100 v=127

タイプ 100,101,6 を全て表示してますが、見たいのは

type=101 v=0
type=100 v=0

の直後の type=6 の値で、それは全て v=12 のようです

先にみた pitch wheel change イベントが存在しない、 MIDIチャンネル 3 などでも、 とりあえず 1オクターブの設定にしてるようですね

ピッチ・ベンド対応

それでは、pitch wheel changeイベントに対応してみましょう

pitch wheel changeイベントは、指定のMIDIチャンネル全体に効きます。

RPNによる最大設定もまた、MIDIチャンネルに対するコントロール・チェンジ なので、指定のMIDIチャンネルで同じ設定になります。

MIDIチャンネル毎に保持する設定情報として

を追加します。

ソース・コードでは、次の配列の箇所になります

#define MIDI_CH_N	16
  :
int ch_bend[ MIDI_CH_N ];
int ch_rpn[ MIDI_CH_N ];
int ch_bend_range[ MIDI_CH_N ];

pitch wheel change イベントがきたら、 イベントの値を、-8192〜8191の値に換算して、該当チャンネルの ch_bend[] へ格納します。

なので、初期値は 0 です

コントロール・チェンジの 100 や 101 がきたら、 該当チャンネルの ch_rpn[] の値の LSB 7ビット, MSB 7ビットを設定します。

初期値はとりあえず 0 にしておいても、問題ないでしょう

コントロール・チェンジの 6 がきたら、 該当チャンネルの ch_rpn[] の値を確認します。 RPN が 0 でなければ、今のところ特に対応しないので何もしません。

RPN が 0 ならば、コントロール・チェンジの値 (半音の数)を、 該当チャンネルの ch_bned_range[] に設定します

data_out() 関数の、波形生成箇所

for(i=0; i<NOTE_BUF_N; i++){
	if(note_buf_is_free(i)) continue;
	freq = note_to_freq(note_buf[i].note);

ここで、ビブラートのときと同様に、freq の値を変化させます。

該当チャンネルの ch_bend[] が 0 なら変化なしなので、なにもしません。

ch_bend[] が 0 以外ならば、ch_bend_range[] を参照して、変化量を決めます。

生成する波形の鍵盤情報のMIDIチャンネル note_buf[i].ch をよく参照するので、nch として変数を追加しておきます。

for(i=0; i<NOTE_BUF_N; i++){
	if(note_buf_is_free(i)) continue;
	nch = note_buf[i].ch;
	freq = note_to_freq(note_buf[i].note);
	if(ch_bend[nch] != 0) freq *= pow(2, ch_bend[nch] / 8192.0 * ch_bend_range[nch] / 12);

この処理でよさげですね。

実は、毎度のこの手の問題があります。

ch_bend[] の値は -8192〜8191 なので、最大の 8191 で freq 倍率は

pow(2, 8191.0/8192.0 * ch_bend_range[nch] / 12)

となります。

ターゲットのSMFでは、ch_bend_range[] は全て 12 の設定だったので、最大で1オクターブです。

本来は、周波数が2倍になってほしいのですが、2^(8191.0/8192.0) 倍、計算すると 1.999831倍程度になります

ターゲットのSMFでは、pitch wheel changeイベントをみると、 最大変化 lsb=127 msb=127 の指定はありませんでした。

逆に、低い側の最大変化 lsb=0 msb=0 はあります。

なので、低い側を正確に 1オクターブ下で合わせておけば大丈夫でしょう :-p)

さて、これで大丈夫かとコーディングしてみると、また問題 (T_T)

pitch wheel change イベントの処理で、note_buf[] の該当チャンネルの サイクルと時刻を更新しようとして、はたと気付きました Σ(*_*)

cycle += freq * (イベント時刻 - sec);
sec = イベント時刻;

この周波数 freq を求めるのが、重たいです (T_T;)

(ビブラートなんぞ試してたりするし...)

ということで、安易に周波数もnote_buf[] に記録してしまいます m(__)m

プログラム

prog6.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
int ch_vol[ MIDI_CH_N ];
int ch_pan[ MIDI_CH_N ];
int ch_bend[ MIDI_CH_N ];
int ch_rpn[ MIDI_CH_N ];
int ch_bend_range[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i, nch;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nch = note_buf[i].ch;
			freq = note_to_freq(note_buf[i].note);
			if(ch_bend[nch] != 0) freq *= pow(2, ch_bend[nch] / 8192.0 * ch_bend_range[nch] / 12);
			dsec = sec - note_buf[i].on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			note_buf[i].cycle += freq * (sec - note_buf[i].sec);
			note_buf[i].sec = sec;
			note_buf[i].freq = freq;
			v = sin(2 * M_PI * note_buf[i].cycle);
			v *= note_buf[i].velo / 127.0;
			v *= ch_vol[nch] / (double)((1<<14)-1);
			if(note_buf[i].onoff == 0){
				dsec = sec - note_buf[i].off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_pan[nch] / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
bend_note_update(int ch, double sec)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		if(note_buf[i].ch != ch) continue;
		note_buf[i].cycle += note_buf[i].freq * (sec - note_buf[i].sec);
		note_buf[i].sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_vol[i] = 0;
		ch_pan[i] = 1<<(14-1);
		ch_bend[i] = 0;
		ch_rpn[i] = 0;
		ch_bend_range[i] = 2;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				ch_vol[ch] &= ~(127<<7);
				ch_vol[ch] |= v<<7;
				break;
			case 39: /* channel volume lsb */
				ch_vol[ch] &= ~127;
				ch_vol[ch] |= v;
				break;
			case 10: /* pan msb */
				ch_pan[ch] &= ~(127<<7);
				ch_pan[ch] |= v<<7;
				break;
			case 42: /* pan lsb */
				ch_pan[ch] &= ~127;
				ch_pan[ch] |= v;
				break;
			case 100: /* rpn lsb */
				ch_rpn[ch] &= ~127;
				ch_rpn[ch] |= v;
				break;
			case 101: /* rpn msb */
				ch_rpn[ch] &= ~(127<<7);
				ch_rpn[ch] |= v<<7;
				break;
			case 6: /* data entry msb */
				switch(ch_rpn[ch]){
				case 0: /* pitch bend range */
					ch_bend_range[ch] = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_bend[ch] = v - 8192;
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog6 prog6.c -lm

$ cat L3007_06.MID | ./prog6 -r 44100 -b 16 -s -c 2 -play

prog6_xaa.mp3

prog6_xab.mp3

エンディングの最後が、特徴的になりました (^_^)

それにしても、Bメロあたりの音のシャクリ上げ方は、本当にこれであってるのだろうか...

ソース・コードを少々整理しておきます。

MIDIチャンネル毎に保持してる情報が増えてきたので、構造体にまとめておきます

プログラム

prog7.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
} ch_inf[ MIDI_CH_N ];

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	double release;
	int i, nch;
	struct note_rec *nt;

	release = 0.3;
	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			freq = note_to_freq(nt->note);
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = sin(2 * M_PI * nt->cycle);
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(nt->onoff == 0){
				dsec = sec - nt->off_sec;
				v *= 1 - dsec / release;
				if(dsec >= release) note_buf_free(i);
			}
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog7 prog7.c -lm

$ cat L3007_06.MID | ./prog6 -r 44100 -b 16 -s -c 2 > prog6.raw
$ cat L3007_06.MID | ./prog7 -r 44100 -b 16 -s -c 2 > prog7.raw
$ cmp prog6.raw prog7.raw
$

ソース・コードをいじりましたが、出力結果は同一です


エンベロープ

残響音をつけるために、いわゆるエンベロープのリリース・タイムを持たせてきました。 ここらで、ちゃんとしたエンベロープをつけてみす

シンセサイザのエンベロープとは、鍵盤オン・オフに連動して音量を変化させる制御です。

色々な方式があると思いますが、シンプルな4つのパラメータ ADSR で制御する方法を試します

エンベロープのパラメータ ADSR

A (attack) 鍵盤オン時に、最小音量 (0.0) から最大音量 (1.0)に至るまでの時間
D (decay) 最大音量 (1.0) に至ってから、sustain で設定した音量に至るまでの時間
S (sustain) 鍵盤オン継続で、decay による減衰後に至る音量
R (release) 鍵盤オフ時に、最小音量 (0.0) に至るまでの時間
1 |...
  |        /\
  |       /  \
  |      /    \
  |     /      \
S |... /        \
  |   /          -------------
  |  /                        \
  | /                          \
  |/                            \
0 +---------------------------------
  |  A     | D  |            | R |

A,D,R は時間のパラメータなので単位は秒、 S は音量のパラメータなので、最小音量を 0.0、最大音量を 1.0 としてみます

以前追加した release の処理では、時間は 0.3 秒に固定。

鍵盤オフからの時間とともに、単純に一定の割合で音量を減衰させてました

  :
data_out(struct out_rec *ot, double evt_sec)
  :
	release = 0.3;
  :
				dsec = sec - nt->off_sec;
				v *= 1 - dsec / release;
  :

今回は指数関数(対数関数)を使って、もう少しそれらしく変化させてみます。

値 v が 0.0 から 1.0 まで変化するとき

log10( 1 + v * 9 )

  log10 は常用対数 (底を10とする対数)

の値を使うようにしてみました。

v == 0 のとき、log10( 1 ) は 0
v == 1 のとき、log10( 10 ) は 1

と変わりませんが、間の値が変わります

コンデンサの充電の曲線で、上下ひっくり返すと放電の曲線です。

放電のときは、バケツの底に穴が空いて水位が下がっていく感覚です。

最初は中身の重さで、水位がぐんぐん下がっていきますが、 中身が減って軽くなると、水位低下の速度も落ちてきて、だらだらと下がっていきます

エンベロープの設定は、 MIDIチャンネルごとの情報の構造体に追加して、 チャンネルごとに別の設定ができるようにしました。

が、とりあえず今は、main()関数で、全チャンネルとも 同じ値 (A=0.1, D=0.2, S=0.8, R=1) に設定してます

プログラム

prog8.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	struct env_rec env;
} ch_inf[ MIDI_CH_N ];

double
env_out(struct note_rec *nt)
{
	struct env_rec *e;
	double sec, v;

	sec = nt->sec;
	e = &ch_inf[nt->ch].env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			freq = note_to_freq(nt->note);
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = sin(2 * M_PI * nt->cycle);
			v *= env_out(&note_buf[i]);
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= ch_inf[nch].env.release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		env_set(&ch_inf[i].env, 0.1, 0.2, 0.8, 1);
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc:
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog8 prog8.c -lm
$ cat L3007_06.MID | ./prog8 -r 44100 -b 16 -s -c 2 -play

うっ。playコマンドで under run 表示 (>_<)

私の貧弱な環境では、そろそろ処理が重くなってCPUの限界です

一旦ファイルに落してから再生

$ cat L3007_06.MID | ./prog8 -r 44100 -b 16 -s -c 2 -sox prog8.wav
$ play prog8.wav

prog8_xaa.mp3

prog8_xab.mp3

あるいは、音質を落して

$ cat L3007_06.MID | ./prog8 -r 8000 -b 16 -s -c 2 -play

これまでは、A=0, D=0, S=1, R=0.3 でしたが、 attack 0 から 0.1 の変化で、だいぶ印象が変わりました。


プログラム番号イベントと音色

ここまでスキップしてるイベントの中に「プログラム番号イベント」があります

プログラム番号イベント

指定のMIDIチャンネルに対し、プログラム番号という0から127の値を指定してます。

このイベントで、予めシンセサイザに登録してある128種類の音色(プログラム)から、 1つを選ぶというわけです。

プログラム番号と音色の対応は、当初製品によってバラバラだったはずですが、 それでは不便なので、GM,GS,XGなどの規格で、ある程度統一されています

例えば「GM Level1 音色」などで検索してみると、番号と音色の名前の一覧が得られるでしょう。

さらに、チャンネル 9 (0起源で9、1起源なら10) のドラム・パートについても、 ノート番号毎に割り当てられる、リズム楽器の音色が決まっています

ターゲットの SMF では、どう指定されてるのか見てみます

$ gcc -o prog_onoff8 prog_onoff8.c -lm
$ cat L3007_06.MID | ./prog_onoff8 | grep prog
sec=0.502 prog num ch=0 v=50
sec=0.528 prog num ch=1 v=35
sec=0.553 prog num ch=2 v=48
sec=0.579 prog num ch=3 v=79
sec=0.605 prog num ch=4 v=87
sec=0.630 prog num ch=5 v=81
sec=0.656 prog num ch=6 v=48
sec=0.681 prog num ch=7 v=79
sec=0.707 prog num ch=8 v=50
sec=0.733 prog num ch=9 v=0
sec=0.758 prog num ch=10 v=24
sec=0.784 prog num ch=11 v=67
$

プログラム番号イベントは、全て開始1秒以内にきてて、 0 から 11 チャンネルまで、1度だけ指定されてます。

そして、12から15チャンネルは使ってないのでしょうか?

$ cat L3007_06.MID | ./prog_onoff8 | grep ch= | sed -e 's/.*\(ch=[0-9]*\) .*/\1/' | sort | uniq
ch=0
ch=1
ch=10
ch=11
ch=2
ch=3
ch=4
ch=5
ch=6
ch=7
ch=8
ch=9
$

そのようです

今回たまたまそうなってますが、別に曲の途中でプログラム番号を変更しても、大丈夫なはずです

各チャンネルに設定されたプログラム番号から、 音色の名前を調べると

チャンネル プログラム番号 音色の名前
0 50 ストリングスアンサンブル2
1 35 エレクトリックベース(ピック)
2 48 ティンパニ
3 79 ホイッスル
4 87 リード7(5th)
5 81 リード1(矩形波)
6 48 ティンパニ
7 79 ホイッスル
8 50 ストリングスアンサンブル2
9 0 (?) <ドラム・パート>
10 24 タンゴアコーディオン
11 67 テナーサックス

と同じ音色が割り当てられてます。

鍵盤オン・オフのタイミングを少しずらして、 エフェクタのディレイのような効果を持たせているようです

チャンネル 9 のドラム・パートのノート番号を見てみます

$ cat L3007_06.MID | ./prog_onoff8 | grep 'on.*ch=9' | sed -e 's/.*\(note=[0-9]*\) .*/\1/' | sort | uniq
note=36
note=37
note=40
note=41
note=42
note=46
note=49
note=57
$

鍵盤オン・イベントで、9 種類のノート番号しか使われてません。

ノート番号に対応する音色は

ノート番号 音色の名前
36 バスドラム1
37 サイドスティック
40 エレクトリックスネア
41 ローフロアタム
42 クローズドハイハット
46 オープンハイハット
49 クラッシュシンバル1
57 クラッシュシンバル2

今は、音色は単なるSIN波だけですが、 さまざまな音色の波形を生成出来るようになったら、 MIDIチャンネルごとに、直接音色を割り振るのではなく、 MIDIチャンネルに設定された、プログラム番号ごとに、 音色を割り振ればいいですね

今は、変更できる音色の要素としては、エンベロープくらいです (T_T)

MIDIチャンネルごとに、エンベロープを設定できるようにしてますが、 MIDIチャンネルに設定されてる、プログラム番号によって、 エンベロープを割り当てて、試してみます

ターゲットの SMF で使われている、プログラム番号だけに対応してみます

プログラム番号 音色の名前
50 ストリングスアンサンブル2
35 エレクトリックベース(ピック)
48 ティンパニ
79 ホイッスル
87 リード7(5th)
81 リード1(矩形波)
24 タンゴアコーディオン
67 テナーサックス

適当に、エンベロープを与えてみます。

ストリングスは立上り遅めで、ベースはサスティン・レベル下げめで...

プログラム番号 A D S R
50,48 0.15 0.5 0.8 0.5
35 0.05 0.2 0.4 0.2
79,87,81 0.01 0.2 0.8 0.3
24,67 0 0.3 0.2 0.3

プログラム

prog9.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	struct env_rec env;
} tone_inf[] = {
	{
		{ 0.15, 0.5, 0.8, 0.5 }
	},{
		{ 0.05, 0.2, 0.4, 0.2 }
	},{
		{ 0.01, 0.2, 0.8, 0.3 }
	},{
		{ 0, 0.3, 0.2, 0.3 }
	}
};

struct tone_rec *
tone_get(int prog)
{
	switch(prog){
	case 48: /* timpani */
	case 50: /* strings ensamble 2 */
		return &tone_inf[0];

	case 35: /* electric bass (pick) */
		return &tone_inf[1];

	case 79: /* whistle */
	case 81: /* lead 1 (square) */
	case 87: /* lead 7 (fifths) */
		return &tone_inf[2];

	case 24: /* tango accordion */
	case 67: /* tenor sax */
	default:
		break;
	}
	return &tone_inf[3];
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_ENV(ch)	tone_get(ch_inf[ch].prog)->env

double
env_out(struct note_rec *nt)
{
	struct env_rec *e;
	double sec, v;

	sec = nt->sec;
	e = &CH_ENV(nt->ch);
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			freq = note_to_freq(nt->note);
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = sin(2 * M_PI * nt->cycle);
			v *= env_out(&note_buf[i]);
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= CH_ENV(nch).release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog9 prog9.c -lm
$ cat L3007_06.MID | ./prog9 -r 44100 -b 16 -s -c 2 -sox prog9.wav
$ play prog9.wav

可能なら

$ cat L3007_06.MID | ./prog9 -r 44100 -b 16 -s -c 2 -play

prog9_xaa.mp3

prog9_xab.mp3

主旋律のリード系は立上り鋭めで、 伴奏のストリングス系は立上り滑らかと、違いが出てきました


ノコギリ波と矩形波

変更できる音色の要素がエンベロープだけでしたが、 そろそろSIN波以外の波形でも、音を鳴らしてみます。

SIN波を生成してる箇所は

prog9.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	struct env_rec env;
} tone_inf[] = {
	{
		{ 0.15, 0.5, 0.8, 0.5 }
	},{
		{ 0.05, 0.2, 0.4, 0.2 }
	},{
		{ 0.01, 0.2, 0.8, 0.3 }
	},{
		{ 0, 0.3, 0.2, 0.3 }
	}
};

struct tone_rec *
tone_get(int prog)
{
	switch(prog){
	case 48: /* timpani */
	case 50: /* strings ensamble 2 */
		return &tone_inf[0];

	case 35: /* electric bass (pick) */
		return &tone_inf[1];

	case 79: /* whistle */
	case 81: /* lead 1 (square) */
	case 87: /* lead 7 (fifths) */
		return &tone_inf[2];

	case 24: /* tango accordion */
	case 67: /* tenor sax */
	default:
		break;
	}
	return &tone_inf[3];
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_ENV(ch)	tone_get(ch_inf[ch].prog)->env

double
env_out(struct note_rec *nt)
{
	struct env_rec *e;
	double sec, v;

	sec = nt->sec;
	e = &CH_ENV(nt->ch);
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			freq = note_to_freq(nt->note);
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = sin(2 * M_PI * nt->cycle);
			v *= env_out(&note_buf[i]);
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= CH_ENV(nch).release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	struct env_rec env;
} tone_inf[] = {
	{
		{ 0.15, 0.5, 0.8, 0.5 }
	},{
		{ 0.05, 0.2, 0.4, 0.2 }
	},{
		{ 0.01, 0.2, 0.8, 0.3 }
	},{
		{ 0, 0.3, 0.2, 0.3 }
	}
};

struct tone_rec *
tone_get(int prog)
{
	switch(prog){
	case 48: /* timpani */
	case 50: /* strings ensamble 2 */
		return &tone_inf[0];

	case 35: /* electric bass (pick) */
		return &tone_inf[1];

	case 79: /* whistle */
	case 81: /* lead 1 (square) */
	case 87: /* lead 7 (fifths) */
		return &tone_inf[2];

	case 24: /* tango accordion */
	case 67: /* tenor sax */
	default:
		break;
	}
	return &tone_inf[3];
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_ENV(ch)	tone_get(ch_inf[ch].prog)->env

double
env_out(struct note_rec *nt)
{
	struct env_rec *e;
	double sec, v;

	sec = nt->sec;
	e = &CH_ENV(nt->ch);
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			freq = note_to_freq(nt->note);
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = sin(2 * M_PI * nt->cycle);
			v *= env_out(&note_buf[i]);
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= CH_ENV(nch).release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

の data_out() 関数

v = sin(2 * M_PI * nt->cycle);

まさに sin() 関数そのものです。

この箇所を

v = wave_out(波形のタイプ , サイクル数);

などと置き換えて、SIN波以外の波形も扱えるようにしてみます。

波形のタイプは、とりあえず次の 4 種類

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3
WAVE_SIN SIN波 (従来通り)
WAVE_SAW ノコギリ波
WAVE_SQURE 矩形波
WAVE_NOISE ノイズ

ノイズはおまけです。 引数のサイクル数に関係なく、乱数でノイズにしてます。

(後に、ドラム・パートで使えないかと)

音色の構造体 tone_rec に、波形のタイプのメンバとして int wave を追加します。

波形が変わると、振幅が同じでも従来のSIN波に比べて、音量の印象が変わります。

バランスを調整できるように、tone_rec 構造体に、音量のパラメータも、 double level として追加しておきます。

とりあえず、音色は4種類のままで、プログラム番号との対応もそのままです

struct tone_rec{
	int wave;
	struct env_rec env;
	double level;
} tone_inf[] = {
	{
		WAVE_SAW,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.7
	},{
		WAVE_SAW,
		{ 0.05, 0.2, 0.4, 0.2 },
		0.7
	},{
		WAVE_SQUARE,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.7
	},{
		WAVE_SIN,
		{ 0, 0.3, 0.2, 0.3 },
		1.0
	}
};

struct tone_rec *
tone_get(int prog)
{
	switch(prog){
	case 48: /* timpani */
	case 50: /* strings ensamble 2 */
		return &tone_inf[0];

	case 35: /* electric bass (pick) */
		return &tone_inf[1];

	case 79: /* whistle */
	case 81: /* lead 1 (square) */
	case 87: /* lead 7 (fifths) */
		return &tone_inf[2];

	case 24: /* tango accordion */
	case 67: /* tenor sax */
	default:
		break;
	}
	return &tone_inf[3];
}

プログラム

prog10.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	int wave;
	struct env_rec env;
	double level;
} tone_inf[] = {
	{
		WAVE_SAW,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.7
	},{
		WAVE_SAW,
		{ 0.05, 0.2, 0.4, 0.2 },
		0.7
	},{
		WAVE_SQUARE,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.7
	},{
		WAVE_SIN,
		{ 0, 0.3, 0.2, 0.3 },
		1.0
	}
};

struct tone_rec *
tone_get(int prog)
{
	switch(prog){
	case 48: /* timpani */
	case 50: /* strings ensamble 2 */
		return &tone_inf[0];

	case 35: /* electric bass (pick) */
		return &tone_inf[1];

	case 79: /* whistle */
	case 81: /* lead 1 (square) */
	case 87: /* lead 7 (fifths) */
		return &tone_inf[2];

	case 24: /* tango accordion */
	case 67: /* tenor sax */
	default:
		break;
	}
	return &tone_inf[3];
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_ENV(ch)	tone_get(ch_inf[ch].prog)->env

double
env_out(struct note_rec *nt)
{
	struct env_rec *e;
	double sec, v;

	sec = nt->sec;
	e = &CH_ENV(nt->ch);
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;
	struct tone_rec *tn;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			freq = note_to_freq(nt->note);
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			tn = tone_get(ch_inf[nch].prog);
			v = wave_out(tn->wave, nt->cycle);
			v *= env_out(&note_buf[i]) * tn->level;
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= tn->env.release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog10 prog10.c -lm
$ cat L3007_06.MID | ./prog10 -r 44100 -b 16 -s -c 2 -sox prog10.wav
$ play prog10.wav

可能なら

$ cat L3007_06.MID | ./prog10 -r 44100 -b 16 -s -c 2 -play

prog10_xaa.mp3

prog10_xab.mp3

いきなり、倍音バリバリな音色に変わってしまいました! Σ(~o~)


ドラムもどき

波形にノイズを追加したので、ドラム・パートを解禁してみます。

といっても、ノイズにエンベロープをつけただけの音色なのですが (^_^;

MIDIチャンネル 9 (0起源、1起源なら10) がドラム・パートで、 これまでは note_onoff() 関数の冒頭で、スキップしてきました

  :
void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(ch == 9) return;
  :

ここを削除して、ch == 9 でも有効にします

ドラム・パート以外のチャンネルでは

の関係で、data_out() 関数の処理では

	:
freq = note_to_freq(nt->note);
	:
tn = tone_get(ch_inf[nch].prog);
	:

の箇所で、周波数の算出と、音色の構造体を取得してました

ドラム・パートでは、プログラム番号は 0 固定で (ターゲットのSMFでは、そのようです)、 鍵盤オン・イベントのノート番号で、音色が決まります

なので、tone_get() 関数を改造して、 プログラム番号以外に、ノート番号もパラメータとして渡し、 音色の構造体と同時に、鳴らす音の周波数も返す関数として、統合してみます

tone_get() 関数の内部では、プログラム番号が 0 ならばドラム・パートと判定して、 引数のノート番号の方から、音色を割だして返すようにします。

返り値は音色の構造体のポインタのままで、 周波数の方は、引数で double 型のポインタを渡し、そこに周波数を返します

プログラム

prog11.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	int wave;
	struct env_rec env;
	double level;
} tone_inf[] = {
	{	/* strings */
		WAVE_SAW,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.7
	},{	/* bass */
		WAVE_SAW,
		{ 0.05, 0.2, 0.4, 0.2 },
		0.7
	},{	/* lead */
		WAVE_SQUARE,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.7
	},{	/* SIN */
		WAVE_SIN,
		{ 0, 0.3, 0.2, 0.3 },
		1.0
	}
}, drum_tone_inf[] = {
	{	/* bass */
		WAVE_NOISE,
		{ 0.01, 0.18, 0, 0.18 },
		1.0
	},{	/* snare */
		WAVE_NOISE,
		{ 0, 0.3, 0.3, 0.3 },
		1.0
	},{	/* tom */
		WAVE_NOISE,
		{ 0, 0.3, 2, 0.4 },
		1.0
	},{	/* hi-hat close */
		WAVE_NOISE,
		{ 0, 0.1, 0, 0.1 },
		1.0
	},{	/* hi-hat open */
		WAVE_NOISE,
		{ 0, 0, 1, 0.3 },
		1.0
	},{	/* cymbal */
		WAVE_NOISE,
		{ 0, 0.3, 0.3, 1 },
		1.0
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
		default:
			idx = 3;
			break;
		}
		ret = &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double sec, v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	sec = nt->sec;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;
	struct tone_rec *tn;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			tn = tone_get(CH_PROG(nch), nt->note, &freq);
			if(tn == NULL) continue;
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = wave_out(tn->wave, nt->cycle);
			v *= env_out(&note_buf[i]) * tn->level;
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= tn->env.release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog11 prog11.c -lm
$ cat L3007_06.MID | ./prog11 -r 44100 -b 16 -s -c 2 -sox prog11.wav
$ play prog11.wav

可能なら

$ cat L3007_06.MID | ./prog11 -r 44100 -b 16 -s -c 2 -play

prog11_xaa.mp3

prog11_xab.mp3

ドラムもどきの音がしてます。

でもドラム・パートの波形は全部ホワイト・ノイズなので、 エンベロープの違いだけで、聞き分けるのは無理がありますね。

昔ゲーセンにあったトランプのゲーム機で、よくこんなリズムの音が鳴ってたような...


ローパス・フィルタ

ドラム・パートの音色に違いを出すべく、ローパス・フィルタ LPF を導入してみます。

昔のアナログ・シンセサイザの VCF の部分ですね。

VCO で 元になる波形を作って、VCF でフィルタで加工、VCA でエンベロープをつけてと、 いよいよシンセサイザっぽくなってきます

ローパス・フィルタとして、追加のパラメータは2つ。

カット・オフ周波数と、レゾナンス(Q値)です

ローをパスするフィルタなので、 カット・オフ周波数は、 その周波数より高い周波数成分は通さないようにする、しきいの周波数です

レゾナンス(Q値)は、値を大きくすると、カットオフ周波数付近を強めるので、 特徴のある音色になります

これらのパラメータは、音色ごとに設定を変えたいので、 音色の情報を保持する tone_rec 構造体に、 メンバ lpf_freq, lpf_Q として追加します

実装した LPF のフィルタ処理では、現在の入力値以外に、 過去2つ分の入力値と、出力値を必要とします。

これらの値を、状態として保持せねばなりません。

一つの鍵盤の音に対して、それぞれフィルタ処理を施したいので、 鍵盤の情報を保持してる note_rec 構造体に、 これらの値を状態として持たせます。

過去2つ分の入力値、出力値と、フィルタ処理に必要なパラメータをまとめて、 lpf_rec 構造体を定義し、note_rec 構造体のメンバとして追加しておきます

data_out() 関数の処理では

  :
v = wave_out(tn->wave, nt->cycle);
v *= env_out(&note_buf[i]) * tn->level;
  :

この箇所で、VCO で波形生成し、VCA でエンベロープ付加とレベル調整。

なので、この間に VCF のフィルタ処理を追加します

フィルタの詳細などは、例によって参考にさせていただいた URL におまかせします (^_^;

http://www.g200kg.com/jp/docs/makingvst/04.html

プログラム

prog12.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_init(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	int i;
	double w0, alpha;

	for(i=0; i<4; i++) lpf->in[i] = lpf->out[i] = 0;
	lpf->idx = 0;

	w0 = 2 * M_PI * freq / smp_freq;
	alpha = sin(w0) / (2 * Q);
	lpf->b1 = 1 - cos(w0);
	lpf->b0 = lpf->b2 = lpf->b1 * 0.5;
	lpf->a0 = 1 + alpha;
	lpf->a1 = -2 * cos(w0);
	lpf->a2 = 1 - alpha;
	if(lpf->a0 != 0) lpf->div_a0 = 1 / lpf->a0;
}

double
lpf_out(struct lpf_rec *lpf, double in)
{
	double *out_p;
	int i_1, i_2;

	lpf->in[lpf->idx] = in;
	out_p = &lpf->out[lpf->idx];

	if(lpf->a0 != 0){
		i_1 = (lpf->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (lpf->b0 * in
			+ lpf->b1 * lpf->in[i_1]
			+ lpf->b2 * lpf->in[i_2]
			- lpf->a1 * lpf->out[i_1]
			- lpf->a2 * lpf->out[i_2] ) * lpf->div_a0;
	}else{
		*out_p = 0;
	}
	lpf->idx = (lpf->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
	struct lpf_rec lpf;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	int wave;
	double lpf_freq, lpf_Q;
	struct env_rec env;
	double level;
} tone_inf[] = {
	{	/* strings */
		WAVE_SAW,
		3000, 1.5,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.7
	},{	/* bass */
		WAVE_SAW,
		1000, 1.5,
		{ 0.05, 0.2, 0.4, 0.2 },
		0.7
	},{	/* lead */
		WAVE_SQUARE,
		2000, 4,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.7
	},{	/* SIN */
		WAVE_SIN,
		20000, 0.8,
		{ 0, 0.3, 0.2, 0.3 },
		1.0
	}
}, drum_tone_inf[] = {
	{	/* bass */
		WAVE_NOISE,
		400, 1,
		{ 0.01, 0.18, 0, 0.18 },
		7.0
	},{	/* snare */
		WAVE_NOISE,
		1000, 1.7,
		{ 0, 0.3, 0.3, 0.3 },
		5.0
	},{	/* tom */
		WAVE_NOISE,
		800, 2,
		{ 0, 0.3, 2, 0.4 },
		1.0
	},{	/* hi-hat close */
		WAVE_NOISE,
		16000, 2,
		{ 0, 0.1, 0, 0.1 },
		1.0
	},{	/* hi-hat open */
		WAVE_NOISE,
		16000, 2,
		{ 0, 0, 1, 0.3 },
		1.0
	},{	/* cymbal */
		WAVE_NOISE,
		5000, 3,
		{ 0, 0.3, 0.3, 1 },
		1.0
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
		default:
			idx = 3;
			break;
		}
		ret = &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double sec, v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	sec = nt->sec;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;
	struct tone_rec *tn;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			tn = tone_get(CH_PROG(nch), nt->note, &freq);
			if(tn == NULL) continue;
			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec;
			if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = wave_out(tn->wave, nt->cycle);
			if(nt->lpf.idx < 0) lpf_init(&nt->lpf, ot->smp_freq, tn->lpf_freq, tn->lpf_Q);
			v = lpf_out(&nt->lpf, v);
			v *= env_out(&note_buf[i]) * tn->level;
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= tn->env.release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
		note_buf[i].lpf.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog12 prog12.c -lm
$ cat L3007_06.MID | ./prog12 -r 44100 -b 16 -s -c 2 -sox prog12.wav
$ play prog12.wav

可能なら

$ cat L3007_06.MID | ./prog12 -r 44100 -b 16 -s -c 2 -play

prog12_xaa.mp3

prog12_xab.mp3

これで、倍音バリバリの音色がマイルドになりました。

ドラム・パートも、ホワイト・ノイズに LPF をかけただけですが、 ずいぶんそれらしく聞こえます (^_^)


LFO

これまで全ての音に一律ビブラートをかけてました。

音色ごとに、LFOのパラメータとして設定できるように変更してみます

従来のビブラートの処理は data_out() 関数の次の箇所

if(dsec > 0.3) freq *= pow(2, sin(2 * M_PI * 6 * dsec) / (12 * 4) );

鍵盤オンから0.3秒後に、6 Hz で、上下に鍵盤半音分の1/4の幅、固定です

LFO自身の設定パラメータとしては

そして、このLFO出力で揺さぶりをかける対象は、 ピッチとフィルタのカット・オフ周波数とします

なので、揺さぶる強さのパラメータを2種類追加します。

この強さは、ピッチの場合は、鳴らす音の周波数を揺さぶり、 フィルタの場合は、カット・オフ周波数を揺さぶります。

周波数に対する揺さぶりなので、値の単位は「セント」で表すことにします。

「セント」は半音 (ノート番号) の 1/100 で

とします

LPF と同様に、 これらの音色ごとに設定を変えたいので、 音色の情報を保持する tone_rec 構造体に、 パラメータを追加します。

LFO による変調は、一つの鍵盤の音に対して、それぞれ処理を施しますが、 鍵盤情報を保持してる note_rec 構造体へのメンバの追加はありません。

LFO 波形の算出には、時刻がわかれば十分で、 保存しておく状態は特にありませんでした。

LFO 自身の周波数は、他からの変調を受けない事にするので、 サイクル数などの状態を保存しておく必要もなく、 サンプリング時刻さえわかれば、LFO の波形を算出できるからです。

プログラム

prog13.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	double w0, alpha;

	w0 = 2 * M_PI * freq / smp_freq;
	alpha = sin(w0) / (2 * Q);
	lpf->b1 = 1 - cos(w0);
	lpf->b0 = lpf->b2 = lpf->b1 * 0.5;
	lpf->a0 = 1 + alpha;
	lpf->a1 = -2 * cos(w0);
	lpf->a2 = 1 - alpha;
	if(lpf->a0 != 0) lpf->div_a0 = 1 / lpf->a0;
}

void
lpf_init(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	int i;


	for(i=0; i<4; i++) lpf->in[i] = lpf->out[i] = 0;
	lpf->idx = 0;

	lpf_update(lpf, smp_freq, freq, Q);
}

double
lpf_out(struct lpf_rec *lpf, double in)
{
	double *out_p;
	int i_1, i_2;

	lpf->in[lpf->idx] = in;
	out_p = &lpf->out[lpf->idx];

	if(lpf->a0 != 0){
		i_1 = (lpf->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (lpf->b0 * in
			+ lpf->b1 * lpf->in[i_1]
			+ lpf->b2 * lpf->in[i_2]
			- lpf->a1 * lpf->out[i_1]
			- lpf->a2 * lpf->out[i_2] ) * lpf->div_a0;
	}else{
		*out_p = 0;
	}
	lpf->idx = (lpf->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle, sec, freq;
	double off_v;
	struct lpf_rec lpf;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	int wave;
	double lpf_freq, lpf_Q;
	struct env_rec env;
	double level;
	int lfo_p, lfo_f;
	int lfo_wave;
	double lfo_freq, lfo_delay;
} tone_inf[] = {
	{	/* strings */
		WAVE_SAW,
		3000, 1.5,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.7,
		25, 0, WAVE_SIN, 6, 0.3,
	},{	/* bass */
		WAVE_SAW,
		1000, 1.5,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		60, 0, WAVE_SIN, 1.5, 0.2,
	},{	/* lead */
		WAVE_SQUARE,
		2000, 4,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.5,
		10, 5, WAVE_SIN, 4, 0.3,
	},{	/* SIN */
		WAVE_SIN,
		20000, 0.8,
		{ 0, 0.3, 0.2, 0.3 },
		1.0,
		25, 0, WAVE_SIN, 6, 0.3,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		WAVE_NOISE,
		400, 1,
		{ 0.01, 0.18, 0, 0.18 },
		10.0,
		0, 0, WAVE_SIN, 0, 0,
	},{	/* snare */
		WAVE_NOISE,
		1000, 1.7,
		{ 0, 0.3, 0.3, 0.3 },
		3.0,
		0, 1200, WAVE_NOISE, 0, 0,
	},{	/* tom */
		WAVE_NOISE,
		800, 2,
		{ 0, 0.3, 2, 0.4 },
		1.0,
		0, 0, WAVE_SIN, 0, 0,
	},{	/* hi-hat close */
		WAVE_NOISE,
		16000, 2,
		{ 0, 0.1, 0, 0.1 },
		0.8,
		0, 0, WAVE_SIN, 0, 0,
	},{	/* hi-hat open */
		WAVE_NOISE,
		16000, 2,
		{ 0, 0, 1, 0.3 },
		0.8,
		0, 0, WAVE_SIN, 0, 0,
	},{	/* cymbal */
		WAVE_NOISE,
		5000, 3,
		{ 0, 0.3, 0.3, 1 },
		0.8,
		0, 200, WAVE_SIN, 3, 0.3,
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
		default:
			idx = 3;
			break;
		}
		ret = &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double sec, v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	sec = nt->sec;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, sec, freq, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;
	struct tone_rec *tn;
	double lfo_out;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			tn = tone_get(CH_PROG(nch), nt->note, &freq);
			if(tn == NULL) continue;

			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			dsec = sec - nt->on_sec - tn->lfo_delay;
			if((tn->lfo_p || tn->lfo_f) && dsec > 0){
				lfo_out = wave_out(tn->lfo_wave, tn->lfo_freq * dsec);
			}
			if(tn->lfo_p && dsec > 0) freq *= pow(2, lfo_out * (tn->lfo_p / 100.0) / 12);
			nt->cycle += freq * (sec - nt->sec);
			nt->sec = sec;
			nt->freq = freq;
			v = wave_out(tn->wave, nt->cycle);

			if(nt->lpf.idx < 0) lpf_init(&nt->lpf, ot->smp_freq, tn->lpf_freq, tn->lpf_Q);
			if(tn->lfo_f && dsec > 0){
				freq = tn->lpf_freq * pow(2, lfo_out * (tn->lfo_f / 100.0) / 12);
				lpf_update(&nt->lpf, ot->smp_freq, freq, tn->lpf_Q);
			}
			v = lpf_out(&nt->lpf, v);

			v *= env_out(&note_buf[i]) * tn->level;
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= tn->env.release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle = 0;
		note_buf[i].sec = evt_sec;
		note_buf[i].lpf.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle += nt->freq * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog13 prog13.c -lm
$ cat L3007_06.MID | ./prog13 -r 44100 -b 16 -s -c 2 -sox prog13.wav
$ play prog13.wav

可能なら

$ cat L3007_06.MID | ./prog13 -r 44100 -b 16 -s -c 2 -play

prog13_xaa.mp3

prog13_xab.mp3

ビブラート以外に、フィルタの変調として、シンバルの音に強めにかけてみました。

音の消えぎわに若干「ワウワウ」感が出ててます。

スネア・ドラムでは、フィルタの1000 Hzを中心に、 上下1オクターブ分の幅で、ノイズの波形で揺さぶってみました。

善し悪しはともあれ、多少音色が変わりました


2 VCO

これまでは、SIN波、ノコギリ波、矩形波、ノイズから1つだけ波形を選んで使ってました。

ここで、VCOを2つにして少し音色に厚みを持たせてみます

二種類の波形を選び、比率を指定して混合出来るようにしてみます。

また、2つ目のVCOの周波数は、1つ目のVCOに対して、指定分だけずらせるようにします

2つのVCOの周波数をずらせると、何がいいか?

1オクターブ分ずらして、エフェクタのオクターバをかけたようにも使えますが、 何といってもディチューンの効果でしょう。

2つ目のVCOで、わざと少しだけピッチをずらして鳴らすと、ぐっと音に深みが出ます。

高校生の頃、お金持ちの友達が持ってた Roland JX-3P を借りて、 2VCO のディチューンに触れて、えらく感動したものです。(遠い目)

ということで、プログラムのVCO関連のパラメータは、 xxx を xxx1, xxx2 と2つに分離し、 パラメータを参照してる箇所も2回分にコピーして、xxx1, xxx2 に変更します

鍵盤情報の構造体に保持してる、サイクル数と周波数も、 VCOの状態なのでVCO1, VOC2用に分離して、記録するようにします

2つのVCOの混合比のパラメータ mix は、0.0 指定で VCO1 だけ、 0.5 で 半分半分、1.0 で VCO2 だけとします

プログラム

prog14.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	double w0, alpha;

	w0 = 2 * M_PI * freq / smp_freq;
	alpha = sin(w0) / (2 * Q);
	lpf->b1 = 1 - cos(w0);
	lpf->b0 = lpf->b2 = lpf->b1 * 0.5;
	lpf->a0 = 1 + alpha;
	lpf->a1 = -2 * cos(w0);
	lpf->a2 = 1 - alpha;
	if(lpf->a0 != 0) lpf->div_a0 = 1 / lpf->a0;
}

void
lpf_init(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	int i;


	for(i=0; i<4; i++) lpf->in[i] = lpf->out[i] = 0;
	lpf->idx = 0;

	lpf_update(lpf, smp_freq, freq, Q);
}

double
lpf_out(struct lpf_rec *lpf, double in)
{
	double *out_p;
	int i_1, i_2;

	lpf->in[lpf->idx] = in;
	out_p = &lpf->out[lpf->idx];

	if(lpf->a0 != 0){
		i_1 = (lpf->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (lpf->b0 * in
			+ lpf->b1 * lpf->in[i_1]
			+ lpf->b2 * lpf->in[i_2]
			- lpf->a1 * lpf->out[i_1]
			- lpf->a2 * lpf->out[i_2] ) * lpf->div_a0;
	}else{
		*out_p = 0;
	}
	lpf->idx = (lpf->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	double cycle1, cycle2, sec, freq1, freq2;
	double off_v;
	struct lpf_rec lpf;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 8000);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 8);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-s", ac, av) >= 0);
	ot->ch_num = opt_int("-c", ac, av, 1);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

void
env_set(struct env_rec *e, double a, double d, double s, double r)
{
	e->attack = a;
	e->decay = d;
	e->sustain = s;
	e->release = r;
}

struct tone_rec{
	int wave1, wave2, tune;
	double mix;
	double lpf_freq, lpf_Q;
	struct env_rec env;
	double level;
	int lfo_p1, lfo_p2, lfo_f;
	int lfo_wave;
	double lfo_freq, lfo_delay;
} tone_inf[] = {
	{	/* strings */
		WAVE_SAW, WAVE_SAW, 10, 0.5,
		3000, 1.5,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.8,
		25, 0, 0, WAVE_SIN, 6, 0.3,
	},{	/* bass */
		WAVE_SAW, WAVE_SQUARE, 0, 0.5,
		1000, 1.5,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		0, 60, 0, WAVE_SIN, 1.5, 0.2,
	},{	/* lead */
		WAVE_SQUARE, WAVE_SQUARE, 10, 0.5,
		2000, 4,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.9,
		0, 1, 5, WAVE_SIN, 4, 0.3,
	},{	/* SIN */
		WAVE_SIN, WAVE_SIN, 0, 0,
		20000, 0.8,
		{ 0, 0.3, 0.2, 0.3 },
		1.0,
		25, 0, 0, WAVE_SIN, 6, 0.3,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		WAVE_SIN, WAVE_NOISE, 0, 0.2,
		400, 1,
		{ 0.01, 0.18, 0, 0.18 },
		10.0,
		100, 0, 30, WAVE_NOISE, 0, 0.5,
	},{	/* snare */
		WAVE_NOISE, WAVE_SIN, 0, 0,
		1000, 1.7,
		{ 0, 0.3, 0.3, 0.3 },
		3.0,
		0, 0, 0, WAVE_SIN, 0, 10,
	},{	/* tom */
		WAVE_NOISE, WAVE_SIN, 0, 0,
		800, 2,
		{ 0, 0.3, 2, 0.4 },
		1.0,
		0, 0, 0, WAVE_SIN, 0, 0,
	},{	/* hi-hat close */
		WAVE_NOISE, WAVE_SIN, 0, 0,
		16000, 2,
		{ 0, 0.1, 0, 0.1 },
		0.8,
		0, 0, 0, WAVE_SIN, 0, 0,
	},{	/* hi-hat open */
		WAVE_NOISE, WAVE_SIN, 0, 0,
		16000, 2,
		{ 0, 0, 1, 0.3 },
		0.8,
		0, 0, 0, WAVE_SIN, 0, 0,
	},{	/* cymbal */
		WAVE_NOISE, WAVE_SIN, 0, 0,
		5000, 3,
		{ 0, 0.3, 0.3, 1 },
		0.8,
		0, 0, 200, WAVE_SIN, 3, 0.3,
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
		default:
			idx = 3;
			break;
		}
		ret = &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double sec, v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	sec = nt->sec;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double v, v1, v2, sec, freq, freq1, freq2, dsec;
	double vl, vr, pan;
	int i, nch;
	struct note_rec *nt;
	struct tone_rec *tn;
	double lfo_out;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nch = nt->ch;
			tn = tone_get(CH_PROG(nch), nt->note, &freq);
			if(tn == NULL) continue;

			if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);
			freq1 = freq2 = freq;
			freq2 *= pow(2, (tn->tune / 100.0) / 12);
			dsec = sec - nt->on_sec - tn->lfo_delay;
			if((tn->lfo_p1 || tn->lfo_p2 || tn->lfo_f) && dsec > 0){
				lfo_out = wave_out(tn->lfo_wave, tn->lfo_freq * dsec);
			}
			if(tn->lfo_p1 && dsec > 0) freq1 *= pow(2, lfo_out * (tn->lfo_p1 / 100.0) / 12);
			nt->cycle1 += freq1 * (sec - nt->sec);
			nt->freq1 = freq1;
			if(tn->lfo_p2 && dsec > 0) freq2 *= pow(2, lfo_out * (tn->lfo_p2 / 100.0) / 12);
			nt->cycle2 += freq2 * (sec - nt->sec);
			nt->freq2 = freq2;
			nt->sec = sec;
			v1 = wave_out(tn->wave1, nt->cycle1);
			v2 = wave_out(tn->wave2, nt->cycle2);
			v = v1 * (1 - tn->mix) + v2 * tn->mix;

			if(nt->lpf.idx < 0) lpf_init(&nt->lpf, ot->smp_freq, tn->lpf_freq, tn->lpf_Q);
			if(tn->lfo_f && dsec > 0){
				freq = tn->lpf_freq * pow(2, lfo_out * (tn->lfo_f / 100.0) / 12);
				lpf_update(&nt->lpf, ot->smp_freq, freq, tn->lpf_Q);
			}
			v = lpf_out(&nt->lpf, v);

			v *= env_out(&note_buf[i]) * tn->level;
			v *= nt->velo / 127.0;
			v *= ch_inf[nch].vol / (double)((1<<14)-1);
			if(ot->ch_num == 1){
				vl += v;
			}else{
				pan = ch_inf[nch].pan / (double)(1<<14);
				vl += (1 - pan) * v;
				vr += pan * v;
			}
			if(nt->onoff == 0 && sec - nt->off_sec >= tn->env.release) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		note_buf[i].cycle1 = 0;
		note_buf[i].cycle2 = 0;
		note_buf[i].sec = evt_sec;
		note_buf[i].lpf.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i;
	struct note_rec *nt;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		nt->cycle1 += nt->freq1 * (sec - nt->sec);
		nt->cycle2 += nt->freq2 * (sec - nt->sec);
		nt->sec = sec;
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog14 prog14.c -lm
$ cat L3007_06.MID | ./prog14 -r 44100 -b 16 -s -c 2 -sox prog14.wav
$ play prog14.wav

可能なら

$ cat L3007_06.MID | ./prog14 -r 44100 -b 16 -s -c 2 -play

prog14_xaa.mp3

prog14_xab.mp3

リードの音色は、矩形波で10セント (半音の1/10分) ずらしただけの音色ながら 「パリュ〜ン、キュポ〜ン」と響く感じが心地よいです。

ベース・ドラムは、ノイズにSIN波を追加してノート番号28 (41.2 Hz程度) で鳴らしてみました

ここで、ソース・コードの data_out() 関数が込み入ってきたので、 ロジックは変えずに、整理だけしておきます

主な変更内容です

プログラム

prog15.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	double w0, alpha;

	w0 = 2 * M_PI * freq / smp_freq;
	alpha = sin(w0) / (2 * Q);
	lpf->b1 = 1 - cos(w0);
	lpf->b0 = lpf->b2 = lpf->b1 * 0.5;
	lpf->a0 = 1 + alpha;
	lpf->a1 = -2 * cos(w0);
	lpf->a2 = 1 - alpha;
	if(lpf->a0 != 0) lpf->div_a0 = 1 / lpf->a0;
}

void
lpf_init(struct lpf_rec *lpf, double smp_freq, double freq, double Q)
{
	int i;


	for(i=0; i<4; i++) lpf->in[i] = lpf->out[i] = 0;
	lpf->idx = 0;

	lpf_update(lpf, smp_freq, freq, Q);
}

double
lpf_out(struct lpf_rec *lpf, double in)
{
	double *out_p;
	int i_1, i_2;

	lpf->in[lpf->idx] = in;
	out_p = &lpf->out[lpf->idx];

	if(lpf->a0 != 0){
		i_1 = (lpf->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (lpf->b0 * in
			+ lpf->b1 * lpf->in[i_1]
			+ lpf->b2 * lpf->in[i_2]
			- lpf->a1 * lpf->out[i_1]
			- lpf->a2 * lpf->out[i_2] ) * lpf->div_a0;
	}else{
		*out_p = 0;
	}
	lpf->idx = (lpf->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct lpf_rec lpf;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
};

struct lfo_rec{
	int pitch1, pitch2;
	int filter;
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct tone_rec{
	struct vco_rec vco;
	double lpf_freq, lpf_Q;
	struct env_rec env;
	double level;
	struct lfo_rec lfo;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 10, 0.5 },
		3000, 1.5,
		{ 0.15, 0.5, 0.8, 0.5 },
		0.8,
		{ 25, 0, 0, WAVE_SIN, 6, 0.3 },
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, 0, 0.5 },
		1000, 1.5,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ 0, 60, 0, WAVE_SIN, 1.5, 0.2 },
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5 },
		2000, 4,
		{ 0.01, 0.2, 0.8, 0.3 },
		0.9,
		{ 0, 1, 5, WAVE_SIN, 4, 0.3 },
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, 0, 0 },
		20000, 0.8,
		{ 0, 0.3, 0.2, 0.3 },
		1.0,
		{ 25, 0, 0, WAVE_SIN, 6, 0.3 },
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, 0, 0.2 },
		400, 1,
		{ 0.01, 0.18, 0, 0.18 },
		10.0,
		{ 100, 0, 30, WAVE_NOISE, 0, 0.5 },
	},{	/* snare */
		{ WAVE_NOISE, WAVE_SIN, 0, 0 },
		1000, 1.7,
		{ 0, 0.3, 0.3, 0.3 },
		3.0,
		{ 0, 0, 0, WAVE_SIN, 0, 10 },
	},{	/* tom */
		{ WAVE_NOISE, WAVE_SIN, 0, 0 },
		800, 2,
		{ 0, 0.3, 2, 0.4 },
		1.0,
		{ 0, 0, 0, WAVE_SIN, 0, 0 },
	},{	/* hi-hat close */
		{ WAVE_NOISE, WAVE_SIN, 0, 0 },
		16000, 2,
		{ 0, 0.1, 0, 0.1 },
		0.8,
		{ 0, 0, 0, WAVE_SIN, 0, 0 },
	},{	/* hi-hat open */
		{ WAVE_NOISE, WAVE_SIN, 0, 0 },
		16000, 2,
		{ 0, 0, 1, 0.3 },
		0.8,
		{ 0, 0, 0, WAVE_SIN, 0, 0 },
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SIN, 0, 0 },
		5000, 3,
		{ 0, 0.3, 0.3, 1 },
		0.8,
		{ 0, 0, 200, WAVE_SIN, 3, 0.3 },
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
		default:
			idx = 3;
			break;
		}
		ret = &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double sec, v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	sec = nt->vco_stat[0].sec;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

struct lfo_work_rec{
	int value; /* lfo_rec.pitch1, pitch2 or filter */
	double dsec; /* after delay (sec - on_sec - delay) */
	double out;
};

void
lfo_out(struct lfo_rec *lfo, struct lfo_work_rec *lfo_wk, double sec, double on_sec)
{
	lfo_wk->dsec = sec - on_sec - lfo->delay;
	if((lfo->pitch1 || lfo->pitch2 || lfo->filter) && lfo_wk->dsec > 0){
		lfo_wk->out = wave_out(lfo->wave, lfo->freq * lfo_wk->dsec);
	}
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, struct lfo_work_rec *lfo_wk)
{
	if(lfo_wk->value && lfo_wk->dsec > 0) freq *= pow(2, lfo_wk->out * (lfo_wk->value / 100.0) / 12);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(double lpf_freq, double lpf_Q, struct lpf_rec *lpf, struct out_rec *ot, struct lfo_work_rec *lfo_wk, double v)
{
	if(lpf->idx < 0) lpf_init(lpf, ot->smp_freq, lpf_freq, lpf_Q);
	if(lfo_wk->value && lfo_wk->dsec > 0){
		lpf_freq *= pow(2, lfo_wk->out * (lfo_wk->value / 100.0) / 12);
		lpf_update(lpf, ot->smp_freq, lpf_freq, lpf_Q);
	}
	return lpf_out(lpf, v);
}

int
note_out(struct out_rec *ot, double sec, struct note_rec *nt, double *vl, double *vr)
{
	double v, v1, v2, freq, pan;
	int nch;
	struct tone_rec *tn;
	struct vco_rec *vco;
	struct lfo_rec *lfo;
	struct lfo_work_rec lfo_wk;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);

	lfo = &tn->lfo;
	lfo_out(lfo, &lfo_wk, sec, nt->on_sec);

	vco = &tn->vco;
	lfo_wk.value = lfo->pitch1;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, sec, &lfo_wk);
	freq *= pow(2, (vco->tune / 100.0) / 12);
	lfo_wk.value = lfo->pitch2;
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, sec, &lfo_wk);
	v = v1 * (1 - vco->mix) + v2 * vco->mix;

	lfo_wk.value = lfo->filter;
	v = vcf_out(tn->lpf_freq, tn->lpf_Q, &nt->lpf, ot, &lfo_wk, v);

	v *= env_out(nt) * tn->level;
	v *= nt->velo / 127.0;
	v *= ch_inf[nch].vol / (double)((1<<14)-1);
	if(ot->ch_num == 1){
		*vl += v;
	}else{
	pan = ch_inf[nch].pan / (double)(1<<14);
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return (nt->onoff == 0 && sec - nt->off_sec >= tn->env.release);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double sec, vl, vr;
	int i;
	struct note_rec *nt;

	while((sec = (double)ot->smp_cnt / ot->smp_freq) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, sec, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		note_buf[i].lpf.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog14 prog14.c -lm
$ gcc -o prog15 prog15.c -lm
$ cat L3007_06.MID | ./prog14 -r 44100 -b 16 -s -c 2 > prog14.raw
$ cat L3007_06.MID | ./prog15 > prog15.raw
$ cmp prog14.raw prog15.raw
$

ソース・コードをいじりましたが、出力結果は同一です


リング・モジュレーション

せっかくVCOを2つにしたので、リング・モジュレーションをかけてみます

リング・モジュレーションは、 2つのVCO出力をかけ算する事で、2つの周波数の和と差の成分作り、 より複雑な倍音成分を生み出します。

原理は

sin,cos関数の加法定理からの積和の公式より

sin(a)×sin(b) = -1/2( cos(a+b) - cos(a-b) )

周波数の違うsinをかけ算すると、周波数の和と差のcosに

cos(a) = sin(a + π/2)

なので、位相がずれますが、 周波数の違うsinをかけ算すると、周波数の和と差のsinです

ノコギリ波や矩形波の倍音成分を考えると

A = sin(a) + sin(2a) + sin(3a) + ...
B = sin(b) + sin(2b) + sin(3b) + ...

かけ算のたすきがけで

A×B = sin(a)×(sin(b) + sin(2b) + sin(3b) + ...)
     + sin(2a)×(sin(b) + sin(2b) + sin(3b) + ...)
     + sin(3a)×(sin(b) + sin(2b) + sin(3b) + ...)
       :
     = sin(a)×sin(b) + sin(a)×sin(2b) + sin(a)×sin(3b) + ...
     + sin(2a)×sin(b) + sin(2a)×sin(2b) + sin(2a)×sin(3b) + ...
     + sin(3a)×sin(b) + sin(3a)×sin(2b) + sin(3a)×sin(3b) + ...
       :

これら sin 同士のかけ算群が、それぞれ足し算と引き算の周波数のsin波へと変わるわので、 複雑な周波数の成分になります

パラメータは、構造体 vco_rec に int ring というフラグを追加して、 リング・モジュレーションのON/OFFを指定します。

ONの場合、VCO1の出力とVCO2の出力をかけ算し、 その結果を新たなVCO2の出力とします。

VOC1の出力と元々のVCO2の出力は、半々で足し算して、 新たなVCO1の出力とします。

以降、通常通り VCO1 と VCO2 の出力を、指定の比率で混合します

ごたくが長かったですが、処理としての追加箇所は、note_out() 関数

  :
if(vco->ring){
	v = v1 * v2;
	v1 = (v1 + v2) / 2;
	v2 = v;
}
  :

だけで、あとはパラメータの調整になります

ついでにソース・コードの整理も少々

プログラム

prog16.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	double freq, Q;
};

struct lpf_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	double w0, alpha;

	w0 = 2 * M_PI * lpf->freq / smp_freq;
	alpha = sin(w0) / (2 * lpf->Q);
	stat->b1 = 1 - cos(w0);
	stat->b0 = stat->b2 = stat->b1 * 0.5;
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos(w0);
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
lpf_init(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	lpf_update(lpf, stat, smp_freq);
}

double
lpf_out(struct lpf_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct lpf_stat_rec lpf_stat;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct lfo_rec{
	int pitch1, pitch2;
	int filter;
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct tone_rec{
	struct vco_rec vco;
	struct lpf_rec lpf;
	struct env_rec env;
	double level;
	struct lfo_rec lfo;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ 3000, 1.5 },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF, WAVE_SIN, 4, 0.3 },
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ 1000, 1.5} ,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF, WAVE_SIN, 1.5, 0.2 },
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ 2000, 4 },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5, WAVE_SIN, 4, 0.3 },
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ 20000, 0.8 },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF, WAVE_SIN, 6, 0.3 },
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ 400, 1 },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30, WAVE_NOISE, 0, 0.5 },
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 1500, 1.7 },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, },
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 800, 2 },
		{ 0, 0.3, 2, 0.4 },
		2,
		{ OFF, OFF, OFF, },
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, },
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, },
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ 5000, 3 },
		{ 0, 0.3, 0.3, 1 },
		0.8,
		{ OFF, OFF, 200, WAVE_SIN, 3, 0.3 },
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
		default:
			idx = 3;
			break;
		}
		ret = &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double sec, v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	sec = nt->vco_stat[0].sec;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

struct lfo_work_rec{
	int value; /* lfo_rec.pitch1, pitch2 or filter */
	double dsec; /* after delay (sec - on_sec - delay) */
	double out;
};

void
lfo_out(struct lfo_rec *lfo, struct lfo_work_rec *lfo_wk, double sec, double on_sec)
{
	lfo_wk->dsec = sec - on_sec - lfo->delay;
	if((lfo->pitch1 || lfo->pitch2 || lfo->filter) && lfo_wk->dsec > 0){
		lfo_wk->out = wave_out(lfo->wave, lfo->freq * lfo_wk->dsec);
	}
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, struct lfo_work_rec *lfo_wk)
{
	if(lfo_wk->value && lfo_wk->dsec > 0) freq *= pow(2, lfo_wk->out * (lfo_wk->value / 100.0) / 12);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct lpf_rec *lpf, struct lpf_stat_rec *lpf_stat, struct out_rec *ot, struct lfo_work_rec *lfo_wk, double v)
{
	struct lpf_rec lpf_tmp;

	if(lpf_stat->idx < 0) lpf_init(lpf, lpf_stat, ot->smp_freq);
	if(lfo_wk->value && lfo_wk->dsec > 0){
		lpf_tmp = *lpf;
		lpf_tmp.freq *= pow(2, lfo_wk->out * (lfo_wk->value / 100.0) / 12);
		lpf_update(&lpf_tmp, lpf_stat, ot->smp_freq);
	}
	return lpf_out(lpf_stat, v);
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, v1, v2, freq, pan;
	int nch;
	struct tone_rec *tn;
	struct vco_rec *vco;
	struct lfo_rec *lfo;
	struct lfo_work_rec lfo_wk;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);

	lfo = &tn->lfo;
	lfo_out(lfo, &lfo_wk, ot->sec, nt->on_sec);

	vco = &tn->vco;
	lfo_wk.value = lfo->pitch1;
	lfo_wk.value = 0;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, &lfo_wk);
	freq *= pow(2, (vco->tune / 100.0) / 12);
	lfo_wk.value = lfo->pitch2;
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, &lfo_wk);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) / 2;
		v2 = v;
	}
	v = v1 * (1 - vco->mix) + v2 * vco->mix;

	lfo_wk.value = lfo->filter;
	v = vcf_out(&tn->lpf, &nt->lpf_stat, ot, &lfo_wk, v);

	v *= env_out(nt) * tn->level;
	v *= nt->velo / 127.0;
	v *= ch_inf[nch].vol / (double)((1<<14)-1);
	if(ot->ch_num == 1){
		*vl += v;
	}else{
	pan = ch_inf[nch].pan / (double)(1<<14);
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return (nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		note_buf[i].lpf_stat.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i]);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog16 prog16.c -lm
$ cat L3007_06.MID | ./prog16 -sox prog16.wav
$ play prog16.wav

可能なら

$ cat L3007_06.MID | ./prog16 -play

prog16_xaa.mp3

prog16_xab.mp3

ドラム・パートのハイ・ハットの音を、 ノイズから金属っぽい音に変えてみました。

シンバルの音が、なかなかうまく作れないです (>_<)


エンベロープで変調

シンバルやタムの音がうまく作れないのは、 エンベロープに従って音色が変化できないせいでしょうか?

関係無いかも知れませんが、そういう事にしておいて、エンベロープでも変調できるようにしてみます (^_^;

LFOでVCOのピッチとVCFのフィルタのカット・オフ周波数を変調できるようにしたので、 エンベロープの出力でも同様に変調させてみます。

と、その前に、LFOまわりがごちゃごちゃしてきてるので、 ソース・コードを整理しておきます

最後の変更内容について、note_onoff() 関数からの env_out()関数 呼び出しでは、時刻として、evt_sec を指定するべきです。

ところが、以前からのミスで旧env_out() 関数では、 VCO1 で最後に記録された状態の時刻 nt->vco_stat[0].sec を参照してました。

一旦、ソース・コードを整理しても、ロジックが変わってない事を確認するため、 あえて従来通り VCO1 で最後に記録された状態の時刻を指定します

そして、ロジックが変わってない事の確認

プログラム

prog17.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	double freq, Q;
};

struct lpf_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	double w0, alpha;

	w0 = 2 * M_PI * lpf->freq / smp_freq;
	alpha = sin(w0) / (2 * lpf->Q);
	stat->b1 = 1 - cos(w0);
	stat->b0 = stat->b2 = stat->b1 * 0.5;
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos(w0);
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
lpf_init(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	lpf_update(lpf, stat, smp_freq);
}

double
lpf_out(struct lpf_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct lpf_stat_rec lpf_stat;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct tone_rec{
	struct vco_rec vco;
	struct lpf_rec lpf;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ 3000, 1.5 },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF }, { WAVE_SIN, 4, 0.3 },
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ 1000, 1.5} ,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF }, { WAVE_SIN, 1.5, 0.2 },
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ 2000, 4 },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5 }, { WAVE_SIN, 4, 0.3 },
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ 20000, 0.8 },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ 400, 1 },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30 }, { WAVE_NOISE, 0, 0.5 },
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 1500, 1.7 },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 800, 2 },
		{ 0, 0.3, 2, 0.4 },
		2,
		{ OFF, OFF, OFF, }, {},
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, }, {},
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, }, {},
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ 5000, 3 },
		{ 0, 0.3, 0.3, 1 },
		0.8,
		{ OFF, OFF, 200 }, { WAVE_SIN, 3, 0.3 },
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 80;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
			idx = 3;
			break;
		}
		ret = idx < 0 ? NULL : &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 / 100.0 / 12;
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 / 100.0 / 12;
	if(modu->filter) ret_arr[2] += v * modu->filter / 100.0 / 12;
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v /*/ 12*/);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct lpf_rec *lpf, struct lpf_stat_rec *lpf_stat, struct out_rec *ot, double modu_v, double v)
{
	struct lpf_rec lpf_tmp;

	if(lpf_stat->idx < 0) lpf_init(lpf, lpf_stat, ot->smp_freq);
	if(modu_v != 0){
		lpf_tmp = *lpf;
		lpf_tmp.freq *= pow(2, modu_v /*/ 12*/);
		lpf_update(&lpf_tmp, lpf_stat, ot->smp_freq);
	}
	return lpf_out(lpf_stat, v);
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, v1, v2, freq, pan, lfo_v, env_v, modu_v[3];
	int nch;
	struct tone_rec *tn;
	struct vco_rec *vco;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);
	env_v = env_out(nt, ot->sec);

	modu_v[0] = modu_v[1] = modu_v[2] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, modu_v[0]);

	freq *= pow(2, (vco->tune / 100.0) / 12);
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) / 2;
		v2 = v;
	}
	v = v1 * (1 - vco->mix) + v2 * vco->mix;

	v = vcf_out(&tn->lpf, &nt->lpf_stat, ot, modu_v[2], v);

	v *= env_v * tn->level;
	v *= nt->velo / 127.0;
	v *= ch_inf[nch].vol / (double)((1<<14)-1);
	if(ot->ch_num == 1){
		*vl += v;
	}else{
	pan = ch_inf[nch].pan / (double)(1<<14);
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return (nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		note_buf[i].lpf_stat.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i], note_buf[i].vco_stat[0].sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

$ gcc -o prog16 prog16.c -lm
$ gcc -o prog17 prog17.c -lm
$ cat L3007_06.MID | ./prog16 > prog16.raw
$ cat L3007_06.MID | ./prog17 > prog17.raw
$ cmp prog16.raw prog17.raw
$

ソース・コードをいじりましたが、出力結果は同一です

めでたしめでたし。

確認がとれたということで、次の prog18 では、 note_onoff() 関数の

note_buf[i].off_v = env_out(&note_buf[i], note_buf[i].vco_stat[0].sec);

の行を、しれっと

note_buf[i].off_v = env_out(&note_buf[i], evt_sec);

に修正しておきます :-p)

それでは、本題のエンベロープによる変調です

ソース・コードを整理したときに、予め他の変調を追加しやすくしておいたので、 変更量は少しです :-p)

あとは、パラメータの調整です。

変調とは関係ないですが、 ドラム・パートのサイド・スティック (ノート番号37) の音色を追加してみます。

間奏のとこくらいしか使ってなさげなので、 ここまではスネア・ドラムと同じ音色でしのいできました

$ gcc -o prog_onoff8 prog_onoff8.c -lm
$ cat L3007_06.MID | ./prog_onoff8 | grep 'on ch=9 ' | grep note=37
sec=189.344 on ch=9 note=37 velo=84
sec=189.959 on ch=9 note=37 velo=84
sec=190.205 on ch=9 note=37 velo=84
sec=190.574 on ch=9 note=37 velo=84
sec=191.311 on ch=9 note=37 velo=84
sec=191.803 on ch=9 note=37 velo=84
sec=192.172 on ch=9 note=37 velo=84
sec=192.541 on ch=9 note=37 velo=84
sec=193.279 on ch=9 note=37 velo=84
sec=193.770 on ch=9 note=37 velo=84
sec=194.139 on ch=9 note=37 velo=84
sec=194.508 on ch=9 note=37 velo=84
sec=195.246 on ch=9 note=37 velo=84
sec=197.213 on ch=9 note=37 velo=84
sec=197.828 on ch=9 note=37 velo=84
sec=198.074 on ch=9 note=37 velo=84
sec=198.443 on ch=9 note=37 velo=84
sec=199.180 on ch=9 note=37 velo=84
sec=199.672 on ch=9 note=37 velo=84
sec=200.041 on ch=9 note=37 velo=84
sec=200.410 on ch=9 note=37 velo=84
sec=201.147 on ch=9 note=37 velo=84
sec=201.639 on ch=9 note=37 velo=84
sec=202.008 on ch=9 note=37 velo=84
sec=202.377 on ch=9 note=37 velo=84
sec=203.115 on ch=9 note=37 velo=84
sec=203.606 on ch=9 note=37 velo=84
$

確かに。

サイド・スティック (ノート番号37) は、 曲開始から 3分あたりの間奏の15秒間程度だけですね

さて、シンバル (ノート番号 49,57)と、 フロア・タム (ノート番号 41) です。

エンベロープ変調を使うべく、パラメータをいじっくってみましたが...

$ cat L3007_06.MID | ./prog_onoff8 | grep 'on ch=9 ' | grep -e 'note=49' -e 'note=57'
sec=27.541 on ch=9 note=49 velo=65
sec=59.016 on ch=9 note=49 velo=65
sec=66.885 on ch=9 note=49 velo=65
sec=88.033 on ch=9 note=57 velo=65
sec=90.492 on ch=9 note=49 velo=65
sec=114.098 on ch=9 note=57 velo=65
sec=131.803 on ch=9 note=57 velo=65
  :
sec=345.600 on ch=9 note=57 velo=65
sec=346.523 on ch=9 note=49 velo=65
sec=346.523 on ch=9 note=57 velo=65
$
$ cat L3007_06.MID | ./prog_onoff8 | grep 'on ch=9 ' | grep note=41
sec=196.475 on ch=9 note=41 velo=79
$

シンバルはよく鳴ってますが、フロア・タムは1回使うだけでした Σ(T_T)

プログラム

prog18.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	double freq, Q;
};

struct lpf_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	double w0, alpha;

	w0 = 2 * M_PI * lpf->freq / smp_freq;
	alpha = sin(w0) / (2 * lpf->Q);
	stat->b1 = 1 - cos(w0);
	stat->b0 = stat->b2 = stat->b1 * 0.5;
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos(w0);
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
lpf_init(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	lpf_update(lpf, stat, smp_freq);
}

double
lpf_out(struct lpf_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct lpf_stat_rec lpf_stat;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct tone_rec{
	struct vco_rec vco;
	struct lpf_rec lpf;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ 3000, 1.5 },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF },
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ 1000, 1.5} ,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF },
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ 2000, 4 },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5 }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF },
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ 20000, 0.8 },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF },
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ 400, 1 },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30 }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF },
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 1500, 1.7 },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ 800, 2.5 },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
		{ 200, OFF, 1200 },
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ 4000, 2 },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF }, {},
		{ OFF, OFF, 600 },
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ 1700, 3 },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF },
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
			idx = 6;
			note = 110;
			break;
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 100;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
			idx = 3;
			break;
		}
		ret = idx < 0 ? NULL : &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 / 100.0 / 12;
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 / 100.0 / 12;
	if(modu->filter) ret_arr[2] += v * modu->filter / 100.0 / 12;
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v /*/ 12*/);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct lpf_rec *lpf, struct lpf_stat_rec *lpf_stat, struct out_rec *ot, double modu_v, double v)
{
	struct lpf_rec lpf_tmp;

	if(lpf_stat->idx < 0) lpf_init(lpf, lpf_stat, ot->smp_freq);
	if(modu_v != 0){
		lpf_tmp = *lpf;
		lpf_tmp.freq *= pow(2, modu_v /*/ 12*/);
		lpf_update(&lpf_tmp, lpf_stat, ot->smp_freq);
	}
	return lpf_out(lpf_stat, v);
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, v1, v2, freq, pan, lfo_v, env_v, modu_v[3];
	int nch;
	struct tone_rec *tn;
	struct vco_rec *vco;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);
	env_v = env_out(nt, ot->sec);

	modu_v[0] = modu_v[1] = modu_v[2] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, modu_v[0]);

	freq *= pow(2, (vco->tune / 100.0) / 12);
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) / 2;
		v2 = v;
	}
	v = v1 * (1 - vco->mix) + v2 * vco->mix;

	v = vcf_out(&tn->lpf, &nt->lpf_stat, ot, modu_v[2], v);

	v *= env_v * tn->level;
	v *= nt->velo / 127.0;
	v *= ch_inf[nch].vol / (double)((1<<14)-1);
	if(ot->ch_num == 1){
		*vl += v;
	}else{
	pan = ch_inf[nch].pan / (double)(1<<14);
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return (nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		note_buf[i].lpf_stat.idx = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i], evt_sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog18 prog18.c -lm
$ cat L3007_06.MID | ./prog18 -sox prog18.wav
$ play prog18.wav

可能なら

$ cat L3007_06.MID | ./prog18 -play

prog18_xaa.mp3

prog18_xab.mp3

それでもやっぱりシンバルの音が、それらしく聞こえませんね (T_T)


エフェクタ

シンバルの音が気に入らないのはひとまず置いといて、簡単なエフェクタをつけてみます。

ディレイとコーラスだけです

ディレイのパラメータ

コーラスのパラメータは、コーラス有効/無効のフラグだけとしておきます

ディレイの動作の原理は、出力の値をリング・バッファに記録しておいて、 過去の出力を取り出してきて、係数をかけ算して、足し込みます

コーラスは1回だけのディレイですが、遅延時間をSIN波で揺さぶってみます

これらの効果は、音色ごとに変えたいので、 構造体 tone_rec にパラメータを追加します

ディレイ用のリング・バッファは、 1つの鍵盤の音ごとに持たせるようにします。(贅沢!)

音色ごとに出力をまとめていれば、そこで持たせれば良かったかも知れません。

1つの鍵盤の音とごにエフェクタの状態を持たせる事にして、 鍵盤情報の構造体 note_rec に、ディレイ用のリング・バッファを追加します

プログラム

prog19.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


struct lpf_rec{
	double freq, Q;
};

struct lpf_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	double w0, alpha;

	w0 = 2 * M_PI * lpf->freq / smp_freq;
	alpha = sin(w0) / (2 * lpf->Q);
	stat->b1 = 1 - cos(w0);
	stat->b0 = stat->b2 = stat->b1 * 0.5;
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos(w0);
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
lpf_init(struct lpf_rec *lpf, struct lpf_stat_rec *stat, int smp_freq)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	lpf_update(lpf, stat, smp_freq);
}

double
lpf_out(struct lpf_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct lpf_stat_rec lpf_stat;
	int fst_smp_cnt;
	double delay_buf[ DELAY_BUF_N ];
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v *ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct lpf_rec lpf;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ 3000, 1.5 },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ 1000, 1.5} ,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ 2000, 4 },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5 }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ 20000, 0.8 },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ 400, 1 },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30 }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 1500, 1.7 },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ 800, 2.5 },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
		{ 200, OFF, 1200 },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ 4000, 2 },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF }, {},
		{ OFF, OFF, 600 },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ 1700, 3 },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
			idx = 6;
			note = 110;
			break;
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 100;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
			idx = 3;
			break;
		}
		ret = idx < 0 ? NULL : &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 / 100.0 / 12;
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 / 100.0 / 12;
	if(modu->filter) ret_arr[2] += v * modu->filter / 100.0 / 12;
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v /*/ 12*/);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct lpf_rec *lpf, struct lpf_stat_rec *lpf_stat, struct out_rec *ot, double modu_v, double v)
{
	struct lpf_rec lpf_tmp;

	if(lpf_stat->idx < 0) lpf_init(lpf, lpf_stat, ot->smp_freq);
	if(modu_v != 0){
		lpf_tmp = *lpf;
		lpf_tmp.freq *= pow(2, modu_v /*/ 12*/);
		lpf_update(&lpf_tmp, lpf_stat, ot->smp_freq);
	}
	return lpf_out(lpf_stat, v);
}

void
delay_buf_set(struct out_rec *ot, struct note_rec *nt, double v)
{
	nt->delay_buf[ (ot->smp_cnt - nt->fst_smp_cnt) % DELAY_BUF_N ] = v;
}

double
delay_buf_lookup(struct out_rec *ot, struct note_rec *nt, double sec)
{
	double cycle, v1, v2;
	int idx;

	cycle = ot->smp_freq * sec - nt->fst_smp_cnt;
	idx = (int)cycle;
	cycle -= idx;
	v1 = nt->delay_buf[ idx % DELAY_BUF_N ];
	v2 = nt->delay_buf[ (idx + 1 ) % DELAY_BUF_N ];
	return v1 * (1 - cycle) + v2 * cycle;
}

double
delay_out(struct out_rec *ot, struct note_rec *nt, struct delay_rec *delay)
{
	if(ot->sec - nt->on_sec < delay->sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - delay->sec) * delay->gain;
}

double
chorus_out(struct out_rec *ot, struct note_rec *nt)
{
	double dsec, bak_sec;

	dsec = ot->sec - nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - bak_sec);
}

int
delay_fin_chk(struct note_rec *nt, struct out_rec *ot)
{
	int i, n, j;
	double note_freq, v;

	note_freq = nt->vco_stat[0].freq;
	n = ot->smp_freq / note_freq;
	for(i=0; i<n; i++){
		j = (ot->smp_cnt - nt->fst_smp_cnt - i + DELAY_BUF_N) % DELAY_BUF_N;
		v = nt->delay_buf[j];
		if(v < 0) v = -v;
		if(v > 0.0001) break;
	}
	return i >= n;
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, v1, v2, freq, pan, lfo_v, env_v, modu_v[3];
	int nch;
	struct tone_rec *tn;
	struct vco_rec *vco;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(nt->fst_smp_cnt < 0) nt->fst_smp_cnt = ot->smp_cnt;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);
	env_v = env_out(nt, ot->sec);

	modu_v[0] = modu_v[1] = modu_v[2] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, modu_v[0]);

	freq *= pow(2, (vco->tune / 100.0) / 12);
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) / 2;
		v2 = v;
	}
	v = v1 * (1 - vco->mix) + v2 * vco->mix;

	v = vcf_out(&tn->lpf, &nt->lpf_stat, ot, modu_v[2], v);

	v *= env_v * tn->level;

	if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay);
	delay_buf_set(ot, nt, v);
	if(tn->chorus) v += chorus_out(ot, nt);

	v *= nt->velo / 127.0;
	v *= ch_inf[nch].vol / (double)((1<<14)-1);
	if(ot->ch_num == 1){
		*vl += v;
	}else{
	pan = ch_inf[nch].pan / (double)(1<<14);
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return (nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || delay_fin_chk(nt, ot)));
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl / 16);
		if(ot->ch_num > 1) out_do(ot, vr / 16);
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		note_buf[i].lpf_stat.idx = -1;
		note_buf[i].fst_smp_cnt = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i], evt_sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog19 prog19.c -lm
$ cat L3007_06.MID | ./prog19 -sox prog19.wav
$ play prog19.wav

可能なら

$ cat L3007_06.MID | ./prog19 -play

prog19_xaa.mp3

prog19_xab.mp3

とりあえずドラム系はOFF

ストリングスとベースにコーラスを、 リード系にディレイをかけてみました。

リードのしゃくり上げが、多少聴きやすくなりました。

エンディングの最後のベンダの音が、エコーでよりいい感じに (^_^)


処理時間

エフェクタの処理を追加してみて、えらく処理が重くなってました。

歴代のプログラムの処理時間を計ってみます

$ time cat L3007_06.MID | ./prog1 -r 44100 -b 16 -s -c 2 > xxx.raw

real 2m19.640s
user 2m17.385s
sys 0m1.144s
$
  :
$ time cat L3007_06.MID | ./prog14 -r 44100 -b 16 -s -c 2 > xxx.raw

real 6m43.811s
user 6m41.833s
sys 0m0.996s
$

$ time cat L3007_06.MID | ./prog15 > xxx.raw

real 7m21.404s
user 7m20.168s
sys 0m0.628s
$
  :
$ time cat L3007_06.MID | ./prog19 > xxx.raw

real 16m20.967s
user 16m18.725s
sys 0m0.748s
$

などと計ってみて、まとめると

プログラム user time
prog1 2m17.385s
prog2 2m13.588s
prog3 2m14.184s
prog4 2m24.489s
prog5 2m51.067s
prog6 2m50.883s
prog7 2m46.738s
prog8 7m28.884s
prog9 3m54.567s
prog10 3m41.802s
prog11 4m14.404s
prog12 4m36.141s
prog13 5m13.252s
prog14 6m41.833s
prog15 7m20.168s
prog16 7m26.076s
prog17 7m55.782s
prog18 7m53.822s
prog19 16m18.725s

曲の演奏時間は、playコマンドでの表示によると 00:06:01.52 です。

prog8 で一旦6分超えして、prog9 で収まり、prog14 で再び6分超え。

prog18 から prog19 で急激に倍以上に膨らんでます

prog7 から prog8 での増加の原因は、おそらくエンベロープの設定です。

リリース・タイムを 0.3 秒から、1.0 秒に増やしたので、 1つの鍵盤オン・オフの音の処理について、鍵盤オフから 無音までの処理時間が、3倍以上ひっぱる事になります。

prog9 では、リリース・タイムを 0.5 秒以下に抑えた設定にしたので、 処理時間が減りました

更新ごとに順調に(?)増加し、prog14 で演奏時間の 6 分超え

prog18 から prog19 のエフェクタの処理の追加で、ドカンと10分増。

リリース・タイムの処理でひっぱるかわりに、 エフェクタのディレイの残響音の処理で、ひっぱってるという事でしょうか

prog19.c の note_out()関数を少しいじって、処理速度の様子を見てみます

一つのアイデアは、エンベロープ出力に注目します。

エンベロープが 0 まで下がったら、以降その音の出力は 0 なので、 リング・バッファに残ってる出力で、 エフェクタの処理だけすればいいはずです。

ややこしい処理はすっとばして、エフェクタの処理だけにしてみます

$ cp prog19.c tmp.c
tmp.c
  :
int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, v1, v2, freq, pan, lfo_v, env_v, modu_v[3];
	int nch;
	struct tone_rec *tn;
	struct vco_rec *vco;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(nt->fst_smp_cnt < 0) nt->fst_smp_cnt = ot->smp_cnt;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend / 8192.0 * ch_inf[nch].bend_range / 12);

	v = 0;
	// デフォルトとして出力 0 設定

	env_v = env_out(nt, ot->sec);
	// 真っ先にエンベロープ出力を算出

	if(env_v > 0){
	// エンベロープ出力が 0 でなければ、従来通りの処理
	// エンベロープ出力が 0 ならば、音の出力は 0 なので、何もしない

		lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);

		modu_v[0] = modu_v[1] = modu_v[2] = 0;
		modu_out(lfo_v, &tn->lfo_modu, modu_v);
		modu_out(env_v, &tn->env_modu, modu_v);

		vco = &tn->vco;
		v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, modu_v[0]);

		freq *= pow(2, (vco->tune / 100.0) / 12);
		v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, modu_v[1]);
		if(vco->ring){
			v = v1 * v2;
			v1 = (v1 + v2) / 2;
			v2 = v;
		}
		v = v1 * (1 - vco->mix) + v2 * vco->mix;

		v = vcf_out(&tn->lpf, &nt->lpf_stat, ot, modu_v[2], v);

		v *= env_v * tn->level;
	}
	// 以降、従来通り

	if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay);
	delay_buf_set(ot, nt, v);
	if(tn->chorus) v += chorus_out(ot, nt);

	v *= nt->velo / 127.0;
	v *= ch_inf[nch].vol / (double)((1<<14)-1);
	if(ot->ch_num == 1){
		*vl += v;
	}else{
	pan = ch_inf[nch].pan / (double)(1<<14);
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return (nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || delay_fin_chk(nt, ot)));
}
  :
$ gcc -o tmp tmp.c -lm
$ time cat L3007_06.MID | ./tmp > xxx.raw

real 11m14.737s
user 11m11.546s
sys 0m1.532s

16分から11分と、5分程短縮されました

となると次に怪しいのは、エフェクタのディレイの出力が、 減衰しきったか判定するための処理です。

ちょっと実験して確かめてみます。

リリース・タイムが終ったあとまじめに判定せずに、 一律 ディレイの遅延時間分だけひっぱって生存させてみます

$ cp tmp.c tmp2.c
tmp2.c
  :
int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
  :
	return (nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || ot->sec - nt->off_sec - tn->env.release >= tn->delay.sec));
		//(!tn->delay.onoff || delay_fin_chk(nt, ot)));

		// delay_fin_chk() を呼ぶかわりに
		// 一律 delay.sec までひっぱる
}
  :
$ gcc -o tmp2 tmp2.c -lm
$ time cat L3007_06.MID | ./tmp2 > xxx.raw

real 9m31.651s
user 9m28.772s
sys 0m1.696s
$

1、2 分程度の短縮だけでした。

残るはエフェクタの処理自体の重さでしょうか。

エフェクタの処理自体をコメントアウトして確かめてみます

$ cp tmp2.c tmp3.c
tmp3.c
  :
	//if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay);
	//delay_buf_set(ot, nt, v);
	//if(tn->chorus) v += chorus_out(ot, nt);
  :
	return nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release;
	// エフェクタの判定処理をなくして prog19 の状態に
}
$ gcc -o tmp3 tmp3.c -lm
$ time cat L3007_06.MID | ./tmp3 > xxx.raw

real 8m11.241s
user 8m9.527s
sys 0m0.904s
$

ほぼ prog19 の状態に戻してるつもりですが、若干時間がかかってるようです

今どきはコンパイラが勝手に最適化してくれるので、 あまり関係ないとは思うのですが、 念の為、随所に散らばってる割算の処理を、かけ算になおして試してみます

prog19.c から tmp4.c の変更内容

$ gcc -o tmp4 tmp4.c -lm
$ time cat L3007_06.MID | ./tmp4 > xxx.raw

real 14m20.170s
user 14m17.978s
sys 0m0.992s
$

ほほう、これだけで 16分から2分程短縮されました

次は、エフェクタのディレイの出力が、 減衰しきったか判定するための処理を、改善してみます。

リング・バッファに残ってる出力を遡って判定してますが、 ディレイの出力波形と併せて、エンベロープ出力の振幅値も記録しておけば、 バッファを遡って参照しなくても、判定できそうです。

このアイデアでどれだけ短縮できるか試してみます

tmp4.c を元に prog20.c として試してみます

プログラム

prog20.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d -b %d %s -c %d - %s",
			ot->smp_freq, ot->bit_len,
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v * ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

struct lpf_rec{
	double freq, Q;
};

struct lpf_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
lpf_update(struct lpf_rec *lpf, struct lpf_stat_rec *stat, struct out_rec *ot)
{
	double w0, alpha;

	w0 = 2 * M_PI * lpf->freq * ot->smp_t;
	alpha = sin(w0) / (2 * lpf->Q);
	stat->b1 = 1 - cos(w0);
	stat->b0 = stat->b2 = stat->b1 * 0.5;
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos(w0);
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
lpf_init(struct lpf_rec *lpf, struct lpf_stat_rec *stat, struct out_rec *ot)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	lpf_update(lpf, stat, ot);
}

double
lpf_out(struct lpf_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct lpf_stat_rec lpf_stat;
	int fst_smp_cnt;
	double delay_buf[2][ DELAY_BUF_N ];
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

#define OFF		0
#define ON		1

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct lpf_rec lpf;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ 3000, 1.5 },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ 1000, 1.5} ,
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ 2000, 4 },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5 }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ 20000, 0.8 },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ 400, 1 },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30 }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ 1500, 1.7 },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ 800, 2.5 },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, }, {},
		{ 200, OFF, 1200 },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ 16000, 2 },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ 4000, 2 },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF }, {},
		{ OFF, OFF, 600 },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ 1700, 3 },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
			idx = 6;
			note = 110;
			break;
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 100;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
			idx = 3;
			break;
		}
		ret = idx < 0 ? NULL : &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 * (1.0 / 1200);
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 * (1.0 / 1200);
	if(modu->filter) ret_arr[2] += v * modu->filter * (1.0 / 1200);
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct lpf_rec *lpf, struct lpf_stat_rec *lpf_stat, struct out_rec *ot, double modu_v, double v)
{
	struct lpf_rec lpf_tmp;

	if(lpf_stat->idx < 0) lpf_init(lpf, lpf_stat, ot);
	if(modu_v != 0){
		lpf_tmp = *lpf;
		lpf_tmp.freq *= pow(2, modu_v);
		lpf_update(&lpf_tmp, lpf_stat, ot);
	}
	return lpf_out(lpf_stat, v);
}

void
delay_buf_set(struct out_rec *ot, struct note_rec *nt, double v, double env_v)
{
	int i;

	i = (ot->smp_cnt - nt->fst_smp_cnt) % DELAY_BUF_N;
	nt->delay_buf[0][i] = v;
	nt->delay_buf[1][i] = env_v;
}

#define MIX(a, b, rate)		( (a) * (1 - (rate)) + (b) * (rate) )

double
tbl_lookup(double *tbl, int n, double d_idx)
{
	int i;

	i = (int)d_idx;
	d_idx -= i;
	i %= n;
	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
}

double
delay_buf_lookup(struct out_rec *ot, struct note_rec *nt, double sec, double *ret_env_v)
{
	double cycle;

	cycle = ot->smp_freq * sec - nt->fst_smp_cnt;
	if(ret_env_v) *ret_env_v = tbl_lookup(nt->delay_buf[1], DELAY_BUF_N, cycle);
	return tbl_lookup(nt->delay_buf[0], DELAY_BUF_N, cycle);
}

double
delay_out(struct out_rec *ot, struct note_rec *nt, struct delay_rec *delay, double *ret_env_v)
{
	double v;

	if(ot->sec - nt->on_sec < delay->sec) return 0;
	v = delay_buf_lookup(ot, nt, ot->sec - delay->sec, ret_env_v) * delay->gain;
	if(ret_env_v) *ret_env_v *= delay->gain;
	return v;
}

double
chorus_out(struct out_rec *ot, struct note_rec *nt)
{
	double dsec, bak_sec;

	dsec = ot->sec - nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - bak_sec, NULL);
}

double
tone_out(struct out_rec *ot, struct note_rec *nt, struct tone_rec *tn, double freq, double env_v)
{
	double v, v1, v2, lfo_v, modu_v[3];
	struct vco_rec *vco;

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);

	modu_v[0] = modu_v[1] = modu_v[2] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, modu_v[0]);

	modu_v[1] += vco->tune * (1.0 / 1200);
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) * 0.5;
		v2 = v;
	}
	v = MIX(v1, v2, vco->mix);

	v = vcf_out(&tn->lpf, &nt->lpf_stat, ot, modu_v[2], v);

	v *= env_v * tn->level;
	return v;
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, freq, pan, env_v, d_env_v;
	int nch;
	struct tone_rec *tn;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(nt->fst_smp_cnt < 0) nt->fst_smp_cnt = ot->smp_cnt;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend * ch_inf[nch].bend_range * (1.0 / (8192 * 12)));

	env_v = env_out(nt, ot->sec);
	v = (env_v > 0) ? tone_out(ot, nt, tn, freq, env_v) : 0;

	d_env_v = 0;
	if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay, &d_env_v);
	d_env_v += env_v;
	delay_buf_set(ot, nt, v, d_env_v);
	if(tn->chorus) v += chorus_out(ot, nt);

	v *= (nt->velo * (1.0 / 127)) * (ch_inf[nch].vol * (1.0 / ((1<<14)-1)));
	if(ot->ch_num == 1){
		*vl += v;
	}else{
		pan = ch_inf[nch].pan * (1.0 / (1<<14));
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || d_env_v < 0.01);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl * (1.0 / 16));
		if(ot->ch_num > 1) out_do(ot, vr * (1.0 / 16));
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		note_buf[i].lpf_stat.idx = -1;
		note_buf[i].fst_smp_cnt = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i], evt_sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog20 prog20.c -lm
$ time cat L3007_06.MID | ./prog20 > xxx.raw

real 9m56.995s
user 9m54.365s
sys 0m1.620s
$

これで prog19 の 16分から 6分半ほど短縮です

$ cat L3007_06.MID | ./prog20 -sox prog20.wav
$ play prog20.wav

可能なら

$ cat L3007_06.MID | ./prog20 -play

prog20_xaa.mp3

prog20_xab.mp3

割算とかけ算の微妙な違いや、ディレイの減衰判定の違いで、 若干違ってくるはずですが、だいたい同じように聴こえてます


ハイパス・フィルタ

ローパス・フィルタだけでしたが、ハイパス・フィルタも追加してみます

せっかくなので、バンドパス・フィルタも試せるようにしてみます。

フィルタの指定でフィルタのタイプを LPF, HPF, BPF から選ぶようにしておいて、 VCO1, 2 のように FLT1, 2 として、2つまでフィルタを指定できるようにしてみます

あと、違う環境で試してみてふと気付きました Σ(*_*)

最初の方で、soxコマンド(playコマンド)のオプション指定として

-t raw : 生のPCMデータタイプ
-r <サンプリング周波数>
-b 8   : ビット長8ビット
-b 16  : ビット長16ビット
-u     : 符号なし
-s     : 符号あり
-c 1   : モノラル (デフォルト)
-c 2   : ステレオ

  Sox v14.0.1 の場合
  -b : ビット長8ビット
  -w : ビット長16ビット

などと挙げておきながら、 ビット長の指定が、古いバージョンの方に対応できてませんでした

ここまで放置してて今さらですが、対応しておきます m(__)m

プログラム

prog21.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

int
sox_version(void)
{
	FILE *fp;
	int v1, v2, v3;

	if((fp = popen("sox --version | sed -e 's/^.*v//'", "r")) == NULL) return -1;
	fscanf(fp, "%d.%d.%d", &v1, &v2, &v3);
	pclose(fp);
	return v1*10000 + v2*100 + v3;
}

char *
sox_bit_len_fmt(int bit_len)
{
	return sox_version() >= 140400 ?
		(bit_len == 8 ? "-b 8" : "-b 16") :
		(bit_len == 8 ? "-b" : "-w");
}

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d %s %s -c %d - %s",
			ot->smp_freq, sox_bit_len_fmt(ot->bit_len),
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v * ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define LPF		1
#define HPF		2
#define BPF		3

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

struct filter_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
filter_update(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	double w0, alpha, cos_w0;

	if(fl->type == OFF) return;

	w0 = 2 * M_PI * fl->freq * ot->smp_t;
	cos_w0 = cos(w0);
	alpha = sin(w0) / (2 * fl->Q);

	switch(fl->type){
	case LPF:
		stat->b1 = 1 - cos_w0;
		stat->b0 = stat->b2 = stat->b1 * 0.5;
		break;
	case HPF:
		stat->b1 = -(1 + cos_w0);
		stat->b0 = stat->b2 = -stat->b1 * 0.5;
		break;
	case BPF:
		stat->b0 = fl->Q * alpha;
		stat->b1 = 0;
		stat->b2 = -stat->b0;
		break;
	}
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos_w0;
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
filter_init(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	filter_update(fl, stat, ot);
}

double
filter_out(struct filter_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;
	struct vco_stat_rec vco_stat[2];
	double off_v;
	struct filter_stat_rec filter_stat[2];
	int fst_smp_cnt;
	double delay_buf[2][ DELAY_BUF_N ];
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter1, filter2;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ LPF, 1000, 1.5 }, { OFF, },
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ LPF, 2000, 4 }, { OFF, },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ LPF, 20000, 0.8 }, { OFF, },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ LPF, 400, 1 }, { OFF, },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30, OFF }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ LPF, 1500, 1.7 }, { OFF, },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ LPF, 800, 2.5 }, { OFF, },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ 200, OFF, 1200, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ LPF, 4000, 2 }, { OFF, },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, 600, OFF },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ LPF, 1700, 3 }, { OFF, },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
			idx = 6;
			note = 110;
			break;
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
			idx = 5;
			note = 75;
			break;
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 85;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
			idx = 3;
			break;
		}
		ret = idx < 0 ? NULL : &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 * (1.0 / 1200);
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 * (1.0 / 1200);
	if(modu->filter1) ret_arr[2] += v * modu->filter1 * (1.0 / 1200);
	if(modu->filter2) ret_arr[3] += v * modu->filter2 * (1.0 / 1200);
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter1 || modu->filter2)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot, double modu_v, double v)
{
	struct filter_rec tmp;

	if(stat->idx < 0) filter_init(fl, stat, ot);
	if(modu_v != 0){
		tmp = *fl;
		tmp.freq *= pow(2, modu_v);
		filter_update(&tmp, stat, ot);
	}
	return filter_out(stat, v);
}

void
delay_buf_set(struct out_rec *ot, struct note_rec *nt, double v, double env_v)
{
	int i;

	i = (ot->smp_cnt - nt->fst_smp_cnt) % DELAY_BUF_N;
	nt->delay_buf[0][i] = v;
	nt->delay_buf[1][i] = env_v;
}

#define MIX(a, b, rate)		( (a) * (1 - (rate)) + (b) * (rate) )

double
tbl_lookup(double *tbl, int n, double d_idx)
{
	int i;

	i = (int)d_idx;
	d_idx -= i;
	i %= n;
	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
}

double
delay_buf_lookup(struct out_rec *ot, struct note_rec *nt, double sec, double *ret_env_v)
{
	double cycle;

	cycle = ot->smp_freq * sec - nt->fst_smp_cnt;
	if(ret_env_v) *ret_env_v = tbl_lookup(nt->delay_buf[1], DELAY_BUF_N, cycle);
	return tbl_lookup(nt->delay_buf[0], DELAY_BUF_N, cycle);
}

double
delay_out(struct out_rec *ot, struct note_rec *nt, struct delay_rec *delay, double *ret_env_v)
{
	double v;

	if(ot->sec - nt->on_sec < delay->sec) return 0;
	v = delay_buf_lookup(ot, nt, ot->sec - delay->sec, ret_env_v) * delay->gain;
	if(ret_env_v) *ret_env_v *= delay->gain;
	return v;
}

double
chorus_out(struct out_rec *ot, struct note_rec *nt)
{
	double dsec, bak_sec;

	dsec = ot->sec - nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - bak_sec, NULL);
}

double
tone_out(struct out_rec *ot, struct note_rec *nt, struct tone_rec *tn, double freq, double env_v)
{
	int i;
	double v, v1, v2, lfo_v, modu_v[4];
	struct vco_rec *vco;

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);

	for(i=0; i<4; i++) modu_v[i] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->vco_stat[0], freq, ot->sec, modu_v[0]);

	modu_v[1] += vco->tune * (1.0 / 1200);
	v2 = vco_out(vco->wave2, &nt->vco_stat[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) * 0.5;
		v2 = v;
	}
	v = MIX(v1, v2, vco->mix);

	if(tn->fl1.type != OFF) v = vcf_out(&tn->fl1, &nt->filter_stat[0], ot, modu_v[2], v);
	if(tn->fl2.type != OFF) v = vcf_out(&tn->fl2, &nt->filter_stat[1], ot, modu_v[3], v);

	v *= env_v * tn->level;
	return v;
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, freq, pan, env_v, d_env_v;
	int nch;
	struct tone_rec *tn;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(nt->fst_smp_cnt < 0) nt->fst_smp_cnt = ot->smp_cnt;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend * ch_inf[nch].bend_range * (1.0 / (8192 * 12)));

	env_v = env_out(nt, ot->sec);
	v = (env_v > 0) ? tone_out(ot, nt, tn, freq, env_v) : 0;

	d_env_v = 0;
	if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay, &d_env_v);
	d_env_v += env_v;
	delay_buf_set(ot, nt, v, d_env_v);
	if(tn->chorus) v += chorus_out(ot, nt);

	v *= (nt->velo * (1.0 / 127)) * (ch_inf[nch].vol * (1.0 / ((1<<14)-1)));
	if(ot->ch_num == 1){
		*vl += v;
	}else{
		pan = ch_inf[nch].pan * (1.0 / (1<<14));
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || d_env_v < 0.01);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl * (1.0 / 16));
		if(ot->ch_num > 1) out_do(ot, vr * (1.0 / 16));
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, j;
	struct vco_stat_rec *stat;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;
		for(j=0; j<2; j++){
			stat = &note_buf[i].vco_stat[j];
			stat->cycle = 0;
			stat->sec = evt_sec;
		}
		for(j=0; j<2; j++){
			note_buf[i].filter_stat[j].idx = -1;
		}
		note_buf[i].fst_smp_cnt = -1;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].off_v = env_out(&note_buf[i], evt_sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->vco_stat[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog21 prog21.c -lm
$ cat L3007_06.MID | ./prog21 -sox prog21.wav
$ play prog21.wav

可能なら

$ cat L3007_06.MID | ./prog21 -play

ハイパス・フィルタを追加したものの、まだ音色のパラメータはいじってません。

とりあえず LPF だけで、以前のようにちゃんと聴こえるか試してみました


1つの鍵盤で複数の音色

状態の構造体を切り出す

それらしいシンバルの音が作れないので、 1つの鍵盤オン情報で、複数の音色を同時に鳴らしてみて、 なんとかならいものか、試してみます

試すにあたって現状のソース・コードを見直してみると、問題があります

鍵盤オン・オフのイベントで、バッファ確保してる、 note_rec 構造体があります。

ここで鍵盤オン・オフのイベントの情報以外に、 指定の音色で波形生成に関する、各種の状態を持たせてます。

この「状態」の部分が、「1つの音色」前提になってます

まずは、note_rec 構造体から、「状態」の部分を切り離して、 stat_rec 構造体にまとめてみます

切り出した stat_rec 構造体は、 そのまま note_rec 構造体のメンバにさし戻しておいて、 一旦問題ないことを確認します

確認できたら次の段階で、 複数の音色を割り当てて、音色ごとに stat_rec 構造体に「状態」 を持たせるようにしてみます

まずは、stat_rec 構造体の切り出し確認版

プログラム

prog22.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

int
sox_version(void)
{
	FILE *fp;
	int v1, v2, v3;

	if((fp = popen("sox --version | sed -e 's/^.*v//'", "r")) == NULL) return -1;
	fscanf(fp, "%d.%d.%d", &v1, &v2, &v3);
	pclose(fp);
	return v1*10000 + v2*100 + v3;
}

char *
sox_bit_len_fmt(int bit_len)
{
	return sox_version() >= 140400 ?
		(bit_len == 8 ? "-b 8" : "-b 16") :
		(bit_len == 8 ? "-b" : "-w");
}

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d %s %s -c %d - %s",
			ot->smp_freq, sox_bit_len_fmt(ot->bit_len),
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v * ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define LPF		1
#define HPF		2
#define BPF		3

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

struct filter_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
filter_update(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	double w0, alpha, cos_w0;

	if(fl->type == OFF) return;

	w0 = 2 * M_PI * fl->freq * ot->smp_t;
	cos_w0 = cos(w0);
	alpha = sin(w0) / (2 * fl->Q);

	switch(fl->type){
	case LPF:
		stat->b1 = 1 - cos_w0;
		stat->b0 = stat->b2 = stat->b1 * 0.5;
		break;
	case HPF:
		stat->b1 = -(1 + cos_w0);
		stat->b0 = stat->b2 = -stat->b1 * 0.5;
		break;
	case BPF:
		stat->b0 = fl->Q * alpha;
		stat->b1 = 0;
		stat->b2 = -stat->b0;
		break;
	}
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos_w0;
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
filter_init(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	filter_update(fl, stat, ot);
}

double
filter_out(struct filter_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct stat_rec{
	struct vco_stat_rec vco[2];
	struct filter_stat_rec filter[2];
	double off_v;
	int fst_smp_cnt;
	double delay_buf[2][ DELAY_BUF_N ];
};

void
stat_init(struct stat_rec *stat, double sec)
{
	int i;
	struct vco_stat_rec *vco;

	for(i=0; i<2; i++){
		vco = &stat->vco[i];
		vco->cycle = 0;
		vco->sec = sec;
	}
	for(i=0; i<2; i++){
		stat->filter[i].idx = -1;
	}
	stat->fst_smp_cnt = -1;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;

	struct stat_rec stat;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter1, filter2;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ LPF, 1000, 1.5 }, { OFF, },
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ LPF, 2000, 4 }, { OFF, },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ LPF, 20000, 0.8 }, { OFF, },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ LPF, 400, 1 }, { OFF, },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30, OFF }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ LPF, 1500, 1.7 }, { OFF, },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ LPF, 800, 2.5 }, { OFF, },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ 200, OFF, 1200, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ LPF, 4000, 2 }, { OFF, },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, 600, OFF },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ LPF, 1700, 3 }, { OFF, },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_rec *
tone_get(int prog, int note, double *ret_freq)
{
	int idx;
	struct tone_rec *ret;

	idx = -1;
	if(prog <= 0){ /* drum part */
		switch(note){
		case 36: /* bass drum1 */
			idx = 0;
			note = 28;
			break;
		case 37: /* side stick */
			idx = 6;
			note = 110;
			break;
		case 40: /* electric snare */
			idx = 1;
			note = 69;
			break;
		case 41: /* low floor tom */
			idx = 2;
			note = 50;
			break;
		case 42: /* closed hi-hat */
			idx = 3;
			note = 115 + 8;
			break;
		case 46: /* open hi-hat */
			idx = 4;
			note = 115 + 8;
			break;
		case 49: /* crash cymbal 1 */
			idx = 5;
			note = 75;
			break;
		case 57: /* crash cymbal 2 */
			idx = 5;
			note = 85;
			break;
		}
		ret = idx < 0 ? NULL : &drum_tone_inf[idx];
	}else{
		switch(prog){
		case 48: /* timpani */
		case 50: /* strings ensamble 2 */
			idx = 0;
			break;
		case 35: /* electric bass (pick) */
			idx = 1;
			break;
		case 79: /* whistle */
		case 81: /* lead 1 (square) */
		case 87: /* lead 7 (fifths) */
			idx = 2;
			break;
		case 24: /* tango accordion */
		case 67: /* tenor sax */
			idx = 3;
			break;
		}
		ret = idx < 0 ? NULL : &tone_inf[idx];
	}
	if(ret_freq) *ret_freq = note_to_freq(note);
	return ret;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	if((tn = tone_get(CH_PROG(nt->ch), nt->note, NULL)) == NULL) return 0;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->stat.off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 * (1.0 / 1200);
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 * (1.0 / 1200);
	if(modu->filter1) ret_arr[2] += v * modu->filter1 * (1.0 / 1200);
	if(modu->filter2) ret_arr[3] += v * modu->filter2 * (1.0 / 1200);
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter1 || modu->filter2)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot, double modu_v, double v)
{
	struct filter_rec tmp;

	if(stat->idx < 0) filter_init(fl, stat, ot);
	if(modu_v != 0){
		tmp = *fl;
		tmp.freq *= pow(2, modu_v);
		filter_update(&tmp, stat, ot);
	}
	return filter_out(stat, v);
}

void
delay_buf_set(struct out_rec *ot, struct note_rec *nt, double v, double env_v)
{
	int i;

	i = (ot->smp_cnt - nt->stat.fst_smp_cnt) % DELAY_BUF_N;
	nt->stat.delay_buf[0][i] = v;
	nt->stat.delay_buf[1][i] = env_v;
}

#define MIX(a, b, rate)		( (a) * (1 - (rate)) + (b) * (rate) )

double
tbl_lookup(double *tbl, int n, double d_idx)
{
	int i;

	i = (int)d_idx;
	d_idx -= i;
	i %= n;
	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
}

double
delay_buf_lookup(struct out_rec *ot, struct note_rec *nt, double sec, double *ret_env_v)
{
	double cycle;

	cycle = ot->smp_freq * sec - nt->stat.fst_smp_cnt;
	if(ret_env_v) *ret_env_v = tbl_lookup(nt->stat.delay_buf[1], DELAY_BUF_N, cycle);
	return tbl_lookup(nt->stat.delay_buf[0], DELAY_BUF_N, cycle);
}

double
delay_out(struct out_rec *ot, struct note_rec *nt, struct delay_rec *delay, double *ret_env_v)
{
	double v;

	if(ot->sec - nt->on_sec < delay->sec) return 0;
	v = delay_buf_lookup(ot, nt, ot->sec - delay->sec, ret_env_v) * delay->gain;
	if(ret_env_v) *ret_env_v *= delay->gain;
	return v;
}

double
chorus_out(struct out_rec *ot, struct note_rec *nt)
{
	double dsec, bak_sec;

	dsec = ot->sec - nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - bak_sec, NULL);
}

double
tone_out(struct out_rec *ot, struct note_rec *nt, struct tone_rec *tn, double freq, double env_v)
{
	int i;
	double v, v1, v2, lfo_v, modu_v[4];
	struct vco_rec *vco;

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);

	for(i=0; i<4; i++) modu_v[i] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->stat.vco[0], freq, ot->sec, modu_v[0]);

	modu_v[1] += vco->tune * (1.0 / 1200);
	v2 = vco_out(vco->wave2, &nt->stat.vco[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) * 0.5;
		v2 = v;
	}
	v = MIX(v1, v2, vco->mix);

	if(tn->fl1.type != OFF) v = vcf_out(&tn->fl1, &nt->stat.filter[0], ot, modu_v[2], v);
	if(tn->fl2.type != OFF) v = vcf_out(&tn->fl2, &nt->stat.filter[1], ot, modu_v[3], v);

	v *= env_v * tn->level;
	return v;
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, freq, pan, env_v, d_env_v;
	int nch;
	struct tone_rec *tn;

	nch = nt->ch;
	tn = tone_get(CH_PROG(nch), nt->note, &freq);
	if(tn == NULL) return 0;

	if(nt->stat.fst_smp_cnt < 0) nt->stat.fst_smp_cnt = ot->smp_cnt;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend * ch_inf[nch].bend_range * (1.0 / (8192 * 12)));

	env_v = env_out(nt, ot->sec);
	v = (env_v > 0) ? tone_out(ot, nt, tn, freq, env_v) : 0;

	d_env_v = 0;
	if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay, &d_env_v);
	d_env_v += env_v;
	delay_buf_set(ot, nt, v, d_env_v);
	if(tn->chorus) v += chorus_out(ot, nt);

	v *= (nt->velo * (1.0 / 127)) * (ch_inf[nch].vol * (1.0 / ((1<<14)-1)));
	if(ot->ch_num == 1){
		*vl += v;
	}else{
		pan = ch_inf[nch].pan * (1.0 / (1<<14));
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || d_env_v < 0.01);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl * (1.0 / 16));
		if(ot->ch_num > 1) out_do(ot, vr * (1.0 / 16));
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;

		stat_init(&note_buf[i].stat, evt_sec);
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].stat.off_v = env_out(&note_buf[i], evt_sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->stat.vco[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog21 prog21.c -lm
$ gcc -o prog22 prog22.c -lm
$ cat L3007_06.MID | ./prog21 > prog21.raw
$ cat L3007_06.MID | ./prog22 > prog22.raw
$ cmp prog21.raw prog22.raw
$

ソース・コードをいじりましたが、出力結果は同一です

複数の音色を保持する構造

1つの鍵盤オンに対して複数の音色を保持するように、 構造体を変更してみます。

と言っても、構造体に複数の音色を設定できるようにするだけで、 今の段階では、先頭の音色1つだけで音を鳴らします

これまでのプログラムでは、鍵盤オン・オフの情報から note_rec 構造体に

を持たせて、音色の情報が必要になる箇所の、 env_out() 関数、note_out() 関数では、 tone_get() という関数を呼び出して、 tone_rec 構造体を取得していました

この tone_get() 関数が、ごちゃごちゃしてます。

返り値は、tone_rec 構造体のポインタです

1つめの引数のプログラム番号は、 note_rec 構造体の MIDIチャンネルから、 CH_PROG(ch) マクロで、プログラム番号に換算して指定してて、 tone_get() を呼び出しいる2箇所ともそうなってます

この辺りを整理して、1つの note_rec 構造体から、 複数の tone_rec 構造体を管理できるように、変更してみます

まず、tone_compo_rec 構造体を追加します。

音色1つあたりの成分の情報です。

1つの鍵盤オンに対する note_rec 構造体から、 複数個の tone_compo_rec 構造体をヒモ付けるようにします

struct tone_compo_rec{
	struct tone_rec *tone;
	int note; /* -1 : event note */
	double rate;
};

先頭は tone_rec 構造体へのポインタにしました。

これまでのように tone_inf[]、drum_tone_inf[] 配列のインデックスにすると、区別がややこしそうです。

tone_rec 構造体のポインタに統一して扱います

2つめの note は、通常 -1 に設定しておいて、 鍵盤オン・イベントで指定されるノート番号を使います。

ドラム・パートの場合、ここで 0〜127 のノート番号を指定して、 鍵盤オン・イベントで指定されるノート番号は無視して、 ここで指定された値を使います

3つめの rate は、この音色成分の音量を指定します。

生成した波形出力に、この値をかけ算して足し込みます。

次に、MIDIチャンネルと、プログラム番号との関係です。

MIDIチャンネルが決まれば、そのチャンネルに設定されている プログラム番号は決まります。

ドラム・パート以外なら、プログラム番号から音色が決まります。

プログラム番号がドラム・パートなら、さらにノート番号を参照して、 音色が決まります

そこで、従来の tone_get() 関数に替えて、 tone_compo_get() 関数を用意します。

MIDIチャンネルと、ノート番号を引数にして、 対応する tone_compo_rec 構造体の配列を返すようにします。

struct tone_compo_rec *
tone_compo_get(int ch, int note, int *ret_n)

配列を返すといってもC言語なので、 先頭のポインタを返して、要素数は末尾のポインタの引数に返すようにします

さて、tone_compo_get() 関数の実装をどうすべきか...

とりあえず、次に示す構造体のテーブルを用意して、 テーブルの先頭からサーチしてみました

struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst[] = {
	{
		PROG_DRUM, 36, /* bass drum1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[0], 28, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 37, /* side stick */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[6], 110, 1.0 }, { NULL, }
		}
  :
	},{
		24, -1, /* tango accordion */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		67, -1, /* tenor sax */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		-1, /* tail */
	}
};

メンバの prog, note を、引数指定のプログラム番号、ノート番号と比較して、 合致したら tone_compo の配列を返すようにします

が、ここで不正な事をしてるかもしれません。

struct tone_compo_rec *tone_compo;

に対して、初期値

(struct tone_compo_rec []){
	{ tone_compo_rec 構造体1 },
	{ tone_compo_rec 構造体2 },
		:
	{ tone_compo_rec 構造体N },
	{ { NULL, xxx, xxx } }
}

を設定してます。

GCCで通ってますが、普通は許されないのかも知れません。

構造体を int で考えてみると

int a[] = { 1, 2, 3 }; /* OK */
int *b = (int []){ 4, 5, 6 }; /* ? */

と言ったところでしょうか。

さらに char の場合だと

char c[] = { 'a', 'b', 'c', '\0' }; /* OK */
char *d = "def"; /* OK */
char *e = (char []){ 'a', 'b', 'c', '\0' }; /* ? */

char *d の場合と同様で、参照可、代入不可かもしれません。

元に戻って、tone_compo_get() 関数の呼び出しについて。

鍵盤オン・イベントを処理する note_onoff() 関数で、 tone_compo_get() 関数を呼び出して、tone_compo_rec 構造体の配列を取得し、 note_rec 構造体に保持するようにします

音色の情報が必要になる env_out()、note_out() 関数からは、 note_rec 構造体に記録した tone_compo_rec の配列を参照するようにします

ここまで変更してみましたが、各音色の「状態」は、 今のことろ、note_rec 構造体に 1 つだけです。

なので、tone_compo_rec の配列も、先頭の 1 つだけ使うようにして、 従来通り動作するか、試してみます

プログラム

prog23.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

int
sox_version(void)
{
	FILE *fp;
	int v1, v2, v3;

	if((fp = popen("sox --version | sed -e 's/^.*v//'", "r")) == NULL) return -1;
	fscanf(fp, "%d.%d.%d", &v1, &v2, &v3);
	pclose(fp);
	return v1*10000 + v2*100 + v3;
}

char *
sox_bit_len_fmt(int bit_len)
{
	return sox_version() >= 140400 ?
		(bit_len == 8 ? "-b 8" : "-b 16") :
		(bit_len == 8 ? "-b" : "-w");
}

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d %s %s -c %d - %s",
			ot->smp_freq, sox_bit_len_fmt(ot->bit_len),
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v * ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define LPF		1
#define HPF		2
#define BPF		3

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

struct filter_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
filter_update(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	double w0, alpha, cos_w0;

	if(fl->type == OFF) return;

	w0 = 2 * M_PI * fl->freq * ot->smp_t;
	cos_w0 = cos(w0);
	alpha = sin(w0) / (2 * fl->Q);

	switch(fl->type){
	case LPF:
		stat->b1 = 1 - cos_w0;
		stat->b0 = stat->b2 = stat->b1 * 0.5;
		break;
	case HPF:
		stat->b1 = -(1 + cos_w0);
		stat->b0 = stat->b2 = -stat->b1 * 0.5;
		break;
	case BPF:
		stat->b0 = fl->Q * alpha;
		stat->b1 = 0;
		stat->b2 = -stat->b0;
		break;
	}
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos_w0;
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
filter_init(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	filter_update(fl, stat, ot);
}

double
filter_out(struct filter_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct stat_rec{
	struct vco_stat_rec vco[2];
	struct filter_stat_rec filter[2];
	double off_v;
	int fst_smp_cnt;
	double delay_buf[2][ DELAY_BUF_N ];
};

void
stat_init(struct stat_rec *stat, double sec)
{
	int i;
	struct vco_stat_rec *vco;

	for(i=0; i<2; i++){
		vco = &stat->vco[i];
		vco->cycle = 0;
		vco->sec = sec;
	}
	for(i=0; i<2; i++){
		stat->filter[i].idx = -1;
	}
	stat->fst_smp_cnt = -1;
}

struct tone_compo_rec;

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;

	struct stat_rec stat;
	struct tone_compo_rec *tone_compo;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter1, filter2;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ LPF, 1000, 1.5 }, { OFF, },
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ LPF, 2000, 4 }, { OFF, },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ LPF, 20000, 0.8 }, { OFF, },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ LPF, 400, 1 }, { OFF, },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30, OFF }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ LPF, 1500, 1.7 }, { OFF, },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ LPF, 800, 2.5 }, { OFF, },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ 200, OFF, 1200, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ LPF, 4000, 2 }, { OFF, },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, 600, OFF },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ LPF, 1700, 3 }, { OFF, },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_compo_rec{
	struct tone_rec *tone;
	int note; /* -1 : event note */
	double rate;
};

#define PROG_DRUM	0

struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst[] = {
	{
		PROG_DRUM, 36, /* bass drum1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[0], 28, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 37, /* side stick */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[6], 110, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 40, /* electric snare */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[1], 69, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 41, /* low floor tom */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[2], 50, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 42, /* closed hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[3], 115 + 8, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 46, /* open hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[4], 115 + 8, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 49, /* crash cymbal 1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[5], 75, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 57, /* crash cymbal 2 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[5], 85, 1.0 }, { NULL, }
		}
	},{
		48, -1, /* timpani */
		(struct tone_compo_rec []){
			{ &tone_inf[0], -1, 1.0 }, { NULL, }
		}
	},{
		50, -1, /* strings ensamble 2 */
		(struct tone_compo_rec []){
			{ &tone_inf[0], -1, 1.0 }, { NULL, }
		}
	},{
		35, -1, /* electric bass (pick) */
		(struct tone_compo_rec []){
			{ &tone_inf[1], -1, 1.0 }, { NULL, }
		}
	},{
		79, -1, /* whistle */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		81, -1, /* lead 1 (square) */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		87, -1, /* lead 7 (fifths) */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		24, -1, /* tango accordion */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		67, -1, /* tenor sax */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		-1, /* tail */
	}
};

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

struct tone_compo_rec *
tone_compo_get(int ch, int note, int *ret_n)
{
	struct tone_compo_rec *tone_compo;
	int prog, i;

	prog = CH_PROG(ch);
	for(i=0; tones_lst[i].prog >= 0; i++){
		if(tones_lst[i].prog == prog &&
		   (tones_lst[i].note == note || tones_lst[i].note < 0)) break;
	}
	if(tones_lst[i].prog < 0) return NULL; /* not found */
	tone_compo = tones_lst[i].tone_compo;
	if(ret_n){
		for(i=0; tone_compo[i].tone; i++);
		*ret_n = i;
	}
	return tone_compo;
}

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	tn = nt->tone_compo->tone;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->stat.off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 * (1.0 / 1200);
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 * (1.0 / 1200);
	if(modu->filter1) ret_arr[2] += v * modu->filter1 * (1.0 / 1200);
	if(modu->filter2) ret_arr[3] += v * modu->filter2 * (1.0 / 1200);
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter1 || modu->filter2)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot, double modu_v, double v)
{
	struct filter_rec tmp;

	if(stat->idx < 0) filter_init(fl, stat, ot);
	if(modu_v != 0){
		tmp = *fl;
		tmp.freq *= pow(2, modu_v);
		filter_update(&tmp, stat, ot);
	}
	return filter_out(stat, v);
}

void
delay_buf_set(struct out_rec *ot, struct note_rec *nt, double v, double env_v)
{
	int i;

	i = (ot->smp_cnt - nt->stat.fst_smp_cnt) % DELAY_BUF_N;
	nt->stat.delay_buf[0][i] = v;
	nt->stat.delay_buf[1][i] = env_v;
}

#define MIX(a, b, rate)		( (a) * (1 - (rate)) + (b) * (rate) )

double
tbl_lookup(double *tbl, int n, double d_idx)
{
	int i;

	i = (int)d_idx;
	d_idx -= i;
	i %= n;
	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
}

double
delay_buf_lookup(struct out_rec *ot, struct note_rec *nt, double sec, double *ret_env_v)
{
	double cycle;

	cycle = ot->smp_freq * sec - nt->stat.fst_smp_cnt;
	if(ret_env_v) *ret_env_v = tbl_lookup(nt->stat.delay_buf[1], DELAY_BUF_N, cycle);
	return tbl_lookup(nt->stat.delay_buf[0], DELAY_BUF_N, cycle);
}

double
delay_out(struct out_rec *ot, struct note_rec *nt, struct delay_rec *delay, double *ret_env_v)
{
	double v;

	if(ot->sec - nt->on_sec < delay->sec) return 0;
	v = delay_buf_lookup(ot, nt, ot->sec - delay->sec, ret_env_v) * delay->gain;
	if(ret_env_v) *ret_env_v *= delay->gain;
	return v;
}

double
chorus_out(struct out_rec *ot, struct note_rec *nt)
{
	double dsec, bak_sec;

	dsec = ot->sec - nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - bak_sec, NULL);
}

double
tone_out(struct out_rec *ot, struct note_rec *nt, struct tone_rec *tn, double freq, double env_v)
{
	int i;
	double v, v1, v2, lfo_v, modu_v[4];
	struct vco_rec *vco;

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);

	for(i=0; i<4; i++) modu_v[i] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->stat.vco[0], freq, ot->sec, modu_v[0]);

	modu_v[1] += vco->tune * (1.0 / 1200);
	v2 = vco_out(vco->wave2, &nt->stat.vco[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) * 0.5;
		v2 = v;
	}
	v = MIX(v1, v2, vco->mix);

	if(tn->fl1.type != OFF) v = vcf_out(&tn->fl1, &nt->stat.filter[0], ot, modu_v[2], v);
	if(tn->fl2.type != OFF) v = vcf_out(&tn->fl2, &nt->stat.filter[1], ot, modu_v[3], v);

	v *= env_v * tn->level;
	return v;
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, freq, pan, env_v, d_env_v;
	int nch;
	struct tone_rec *tn;

	nch = nt->ch;
	tn = nt->tone_compo->tone;
	freq = note_to_freq(nt->tone_compo->note >= 0 ? nt->tone_compo->note : nt->note);

	if(nt->stat.fst_smp_cnt < 0) nt->stat.fst_smp_cnt = ot->smp_cnt;

	if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend * ch_inf[nch].bend_range * (1.0 / (8192 * 12)));

	env_v = env_out(nt, ot->sec);
	v = (env_v > 0) ? tone_out(ot, nt, tn, freq, env_v) : 0;

	d_env_v = 0;
	if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay, &d_env_v);
	d_env_v += env_v;
	delay_buf_set(ot, nt, v, d_env_v);
	if(tn->chorus) v += chorus_out(ot, nt);

	v *= (nt->velo * (1.0 / 127)) * (ch_inf[nch].vol * (1.0 / ((1<<14)-1)));
	if(ot->ch_num == 1){
		*vl += v;
	}else{
		pan = ch_inf[nch].pan * (1.0 / (1<<14));
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release &&
		(!tn->delay.onoff || d_env_v < 0.01);
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)) note_buf_free(i);
		}
		out_do(ot, vl * (1.0 / 16));
		if(ot->ch_num > 1) out_do(ot, vr * (1.0 / 16));
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, tone_n;
	struct tone_compo_rec *tone_compo;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		note_buf[i].note = note;
		note_buf[i].ch = ch;
		note_buf[i].onoff = 1;
		note_buf[i].on_sec = evt_sec;
		note_buf[i].velo = velo;

		stat_init(&note_buf[i].stat, evt_sec);
		if((tone_compo = tone_compo_get(ch, note, &tone_n)) == NULL){
			note_buf_free(i);
			return;
		}
		note_buf[i].tone_compo = tone_compo;
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		note_buf[i].stat.off_v = env_out(&note_buf[i], evt_sec);
		note_buf[i].onoff = 0;
		note_buf[i].off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(j=0; j<2; j++){
			stat = &nt->stat.vco[j];
			vco_stat_update(stat, stat->freq, sec);
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog22 prog22.c -lm
$ gcc -o prog23 prog23.c -lm
$ cat L3007_06.MID | ./prog22 > prog22.raw
$ cat L3007_06.MID | ./prog23 > prog23.raw
$ cmp prog22.raw prog23.raw
$

ソース・コードをいじりましたが、出力結果は同一です

複数の音色の状態を持てるように

note_rec 構造体に複数の音色をヒモ付けできたので、 次は、note_rec 構造体に複数の音色の「状態」 (波形生成処理の状態) を持たせるようにします

これまでは note_rec 構造体のメンバとして、 stat_rec 構造体 1つだけを持たせていましたが、 ここから、複数の構造体をたぐれるようにします

stat_rec 構造体のメンバに、次のデータを指すポインタを追加して、 チェーンリストの構造で、複数のデータを持たせるようにしてみます

なので、note_rec 構造体のメンバには、 チェーンリストの先頭を保持するポインタと、 現在処理中のデータを指すポインタを追加しておきます

さらに stat_rec 構造体に、 tone_compo_rec 構造体のポインタと、 note_rec 構造体のポインタを追加しておいて、 stat_rec 構造体さえあれば、いろいろと手繰れるようにしておきます

これまでは、波形生成処理で、note_rec 構造体を主体として、 引数をやりとりしてましたが、 stat_rec 構造体を主体として、引数をやりとりした方が、 すっきりとまとまりそうです

そうすれば、note_rec 構造体に現在処理中の音色の「状態」 を指すポインタなど、持たせなくてもいいはずです

が、それは次回ということで、 とりあえずデグレしてないか確認しておきます

プログラム

prog24.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

int
sox_version(void)
{
	FILE *fp;
	int v1, v2, v3;

	if((fp = popen("sox --version | sed -e 's/^.*v//'", "r")) == NULL) return -1;
	fscanf(fp, "%d.%d.%d", &v1, &v2, &v3);
	pclose(fp);
	return v1*10000 + v2*100 + v3;
}

char *
sox_bit_len_fmt(int bit_len)
{
	return sox_version() >= 140400 ?
		(bit_len == 8 ? "-b 8" : "-b 16") :
		(bit_len == 8 ? "-b" : "-w");
}

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d %s %s -c %d - %s",
			ot->smp_freq, sox_bit_len_fmt(ot->bit_len),
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v * ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define LPF		1
#define HPF		2
#define BPF		3

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

struct filter_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
filter_update(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	double w0, alpha, cos_w0;

	if(fl->type == OFF) return;

	w0 = 2 * M_PI * fl->freq * ot->smp_t;
	cos_w0 = cos(w0);
	alpha = sin(w0) / (2 * fl->Q);

	switch(fl->type){
	case LPF:
		stat->b1 = 1 - cos_w0;
		stat->b0 = stat->b2 = stat->b1 * 0.5;
		break;
	case HPF:
		stat->b1 = -(1 + cos_w0);
		stat->b0 = stat->b2 = -stat->b1 * 0.5;
		break;
	case BPF:
		stat->b0 = fl->Q * alpha;
		stat->b1 = 0;
		stat->b2 = -stat->b0;
		break;
	}
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos_w0;
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
filter_init(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	filter_update(fl, stat, ot);
}

double
filter_out(struct filter_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256
#define STAT_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct note_rec;
struct tone_compo_rec;

struct stat_rec{
	struct vco_stat_rec vco[2];
	struct filter_stat_rec filter[2];
	double off_v;
	int fst_smp_cnt;
	double delay_buf[2][ DELAY_BUF_N ];

	struct note_rec *nt;
	struct tone_compo_rec *tone_compo;
	struct stat_rec *next;
} stat_buf[ STAT_BUF_N ];

void
stat_init(struct stat_rec *stat, double sec)
{
	int i;
	struct vco_stat_rec *vco;

	for(i=0; i<2; i++){
		vco = &stat->vco[i];
		vco->cycle = 0;
		vco->sec = sec;
	}
	for(i=0; i<2; i++){
		stat->filter[i].idx = -1;
	}
	stat->fst_smp_cnt = -1;
}

void
stat_buf_init(void)
{
	int i;

	for(i=0; i<STAT_BUF_N; i++) stat_buf[i].nt = NULL;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;

	struct stat_rec *stat; /* current */
	struct stat_rec *stat_lst;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter1, filter2;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ LPF, 1000, 1.5 }, { OFF, },
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ LPF, 2000, 4 }, { OFF, },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ LPF, 20000, 0.8 }, { OFF, },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ LPF, 400, 1 }, { OFF, },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30, OFF }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ LPF, 1500, 1.7 }, { OFF, },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ LPF, 800, 2.5 }, { OFF, },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ 200, OFF, 1200, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ LPF, 4000, 2 }, { OFF, },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, 600, OFF },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ LPF, 1700, 3 }, { OFF, },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_compo_rec{
	struct tone_rec *tone;
	int note; /* -1 : event note */
	double rate;
};

#define PROG_DRUM	0

struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst[] = {
	{
		PROG_DRUM, 36, /* bass drum1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[0], 28, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 37, /* side stick */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[6], 110, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 40, /* electric snare */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[1], 69, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 41, /* low floor tom */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[2], 50, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 42, /* closed hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[3], 115 + 8, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 46, /* open hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[4], 115 + 8, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 49, /* crash cymbal 1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[5], 75, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 57, /* crash cymbal 2 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[5], 85, 1.0 }, { NULL, }
		}
	},{
		48, -1, /* timpani */
		(struct tone_compo_rec []){
			{ &tone_inf[0], -1, 1.0 }, { NULL, }
		}
	},{
		50, -1, /* strings ensamble 2 */
		(struct tone_compo_rec []){
			{ &tone_inf[0], -1, 1.0 }, { NULL, }
		}
	},{
		35, -1, /* electric bass (pick) */
		(struct tone_compo_rec []){
			{ &tone_inf[1], -1, 1.0 }, { NULL, }
		}
	},{
		79, -1, /* whistle */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		81, -1, /* lead 1 (square) */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		87, -1, /* lead 7 (fifths) */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		24, -1, /* tango accordion */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		67, -1, /* tenor sax */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		-1, /* tail */
	}
};

void
stat_lst_free(struct stat_rec *stat)
{
	if(!stat) return;
	stat->nt = NULL;
	stat_lst_free(stat->next);
}

struct stat_rec *
stat_lst_alloc_init(int n, double sec, struct note_rec *nt, struct tone_compo_rec *tone_compo_arr, struct stat_rec *next)
{
	struct stat_rec *stat;
	int i;

	if(n > 1){
		next = stat_lst_alloc_init(n - 1, sec, nt, tone_compo_arr + 1, next);
		if(next == NULL) return NULL;
	}
	for(i=0; i<STAT_BUF_N; i++) if(stat_buf[i].nt == NULL) break;
	if(i >= STAT_BUF_N){
		stat_lst_free(next);
		return NULL;
	}
	stat = &stat_buf[i];
	stat->nt = nt;
	stat->tone_compo = tone_compo_arr;
	stat->next = next;
	stat_init(stat, sec);
	return stat;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

struct tone_compo_rec *
tone_compo_get(int ch, int note, int *ret_n)
{
	struct tone_compo_rec *tone_compo;
	int prog, i;

	prog = CH_PROG(ch);
	for(i=0; tones_lst[i].prog >= 0; i++){
		if(tones_lst[i].prog == prog &&
		   (tones_lst[i].note == note || tones_lst[i].note < 0)) break;
	}
	if(tones_lst[i].prog < 0) return NULL; /* not found */
	tone_compo = tones_lst[i].tone_compo;
	if(ret_n){
		for(i=0; tone_compo[i].tone; i++);
		*ret_n = i;
	}
	return tone_compo;
}

double
env_out(struct note_rec *nt, double sec)
{
	struct tone_rec *tn;
	struct env_rec *e;
	double v;

	tn = nt->stat->tone_compo->tone;
	e = &tn->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * nt->stat->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 * (1.0 / 1200);
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 * (1.0 / 1200);
	if(modu->filter1) ret_arr[2] += v * modu->filter1 * (1.0 / 1200);
	if(modu->filter2) ret_arr[3] += v * modu->filter2 * (1.0 / 1200);
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter1 || modu->filter2)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct filter_rec *fl, struct filter_stat_rec *stat, struct out_rec *ot, double modu_v, double v)
{
	struct filter_rec tmp;

	if(stat->idx < 0) filter_init(fl, stat, ot);
	if(modu_v != 0){
		tmp = *fl;
		tmp.freq *= pow(2, modu_v);
		filter_update(&tmp, stat, ot);
	}
	return filter_out(stat, v);
}

void
delay_buf_set(struct out_rec *ot, struct note_rec *nt, double v, double env_v)
{
	int i;

	i = (ot->smp_cnt - nt->stat->fst_smp_cnt) % DELAY_BUF_N;
	nt->stat->delay_buf[0][i] = v;
	nt->stat->delay_buf[1][i] = env_v;
}

#define MIX(a, b, rate)		( (a) * (1 - (rate)) + (b) * (rate) )

double
tbl_lookup(double *tbl, int n, double d_idx)
{
	int i;

	i = (int)d_idx;
	d_idx -= i;
	i %= n;
	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
}

double
delay_buf_lookup(struct out_rec *ot, struct note_rec *nt, double sec, double *ret_env_v)
{
	double cycle;

	cycle = ot->smp_freq * sec - nt->stat->fst_smp_cnt;
	if(ret_env_v) *ret_env_v = tbl_lookup(nt->stat->delay_buf[1], DELAY_BUF_N, cycle);
	return tbl_lookup(nt->stat->delay_buf[0], DELAY_BUF_N, cycle);
}

double
delay_out(struct out_rec *ot, struct note_rec *nt, struct delay_rec *delay, double *ret_env_v)
{
	double v;

	if(ot->sec - nt->on_sec < delay->sec) return 0;
	v = delay_buf_lookup(ot, nt, ot->sec - delay->sec, ret_env_v) * delay->gain;
	if(ret_env_v) *ret_env_v *= delay->gain;
	return v;
}

double
chorus_out(struct out_rec *ot, struct note_rec *nt)
{
	double dsec, bak_sec;

	dsec = ot->sec - nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(ot, nt, ot->sec - bak_sec, NULL);
}

double
tone_out(struct out_rec *ot, struct note_rec *nt, struct tone_rec *tn, double freq, double env_v)
{
	int i;
	double v, v1, v2, lfo_v, modu_v[4];
	struct vco_rec *vco;

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, nt->on_sec);

	for(i=0; i<4; i++) modu_v[i] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &nt->stat->vco[0], freq, ot->sec, modu_v[0]);

	modu_v[1] += vco->tune * (1.0 / 1200);
	v2 = vco_out(vco->wave2, &nt->stat->vco[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) * 0.5;
		v2 = v;
	}
	v = MIX(v1, v2, vco->mix);

	if(tn->fl1.type != OFF) v = vcf_out(&tn->fl1, &nt->stat->filter[0], ot, modu_v[2], v);
	if(tn->fl2.type != OFF) v = vcf_out(&tn->fl2, &nt->stat->filter[1], ot, modu_v[3], v);

	v *= env_v * tn->level;
	return v;
}

int
note_out(struct out_rec *ot, struct note_rec *nt, double *vl, double *vr)
{
	double v, sum_v, freq, pan, env_v, d_env_v;
	int nch, cnt;
	struct tone_rec *tn;
	struct tone_compo_rec *tone_compo;
	struct stat_rec *stat;

	nch = nt->ch;
	cnt = 0;
	sum_v = 0;
	for(stat=nt->stat_lst; stat; stat=stat->next){
		tone_compo = stat->tone_compo;
		if(tone_compo == NULL) continue;
		cnt++;
		nt->stat = stat; /* back compati */
		tn = tone_compo->tone;
		freq = note_to_freq(tone_compo->note >= 0 ? tone_compo->note : nt->note);

		if(nt->stat->fst_smp_cnt < 0) nt->stat->fst_smp_cnt = ot->smp_cnt;
		if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend * ch_inf[nch].bend_range * (1.0 / (8192 * 12)));

		env_v = env_out(nt, ot->sec);
		v = (env_v > 0) ? tone_out(ot, nt, tn, freq, env_v) : 0;

		d_env_v = 0;
		if(tn->delay.onoff) v += delay_out(ot, nt, &tn->delay, &d_env_v);
		d_env_v += env_v;
		delay_buf_set(ot, nt, v, d_env_v);
		if(tn->chorus) v += chorus_out(ot, nt);

		sum_v += v * tone_compo->rate;

		if(nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release && (!tn->delay.onoff || d_env_v < 0.01)){
			nt->stat->tone_compo = NULL;
		}
	}
	v = sum_v;

	v *= (nt->velo * (1.0 / 127)) * (ch_inf[nch].vol * (1.0 / ((1<<14)-1)));
	if(ot->ch_num == 1){
		*vl += v;
	}else{
		pan = ch_inf[nch].pan * (1.0 / (1<<14));
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return cnt == 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			if(note_out(ot, nt, &vl, &vr)){
				stat_lst_free(nt->stat_lst);
				note_buf_free(i);
			}
		}
		out_do(ot, vl * (1.0 / 16));
		if(ot->ch_num > 1) out_do(ot, vr * (1.0 / 16));
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, tone_n;
	struct note_rec *nt;
	struct stat_rec *stat;
	struct tone_compo_rec *tone_compo;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		nt = &note_buf[i];
		nt->note = note;
		nt->ch = ch;
		nt->onoff = 1;
		nt->on_sec = evt_sec;
		nt->velo = velo;

		if((tone_compo = tone_compo_get(ch, note, &tone_n)) == NULL){
			note_buf_free(i);
			return;
		}
		if((nt->stat_lst = stat_lst_alloc_init(tone_n, evt_sec, nt, tone_compo, NULL)) == NULL){
			note_buf_free(i);
		}
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		nt = &note_buf[i];
		for(stat=nt->stat_lst; stat; stat=stat->next){
			nt->stat = stat; /* back compati */
			nt->stat->off_v = env_out(nt, evt_sec);
		}
		nt->onoff = 0;
		nt->off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *vco_stat;
	struct stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(stat=nt->stat_lst; stat; stat=stat->next){
			for(j=0; j<2; j++){
				vco_stat = &stat->vco[j];
				vco_stat_update(vco_stat, vco_stat->freq, sec);
			}
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	stat_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog23 prog23.c -lm
$ gcc -o prog24 prog24.c -lm
$ cat L3007_06.MID | ./prog23 > prog23.raw
$ cat L3007_06.MID | ./prog24 > prog24.raw
$ cmp prog23.raw prog24.raw
$

ソース・コードをいじりましたが、出力結果は同一です

引数を整理して動作確認

予告通り、いくつかの関数の引数を note_rec 構造体から、 stat_rec 構造体に変更して、ソース・コードを整理しておきます

ついでに note_rec 構造体に out_rec 構造体のポインタを追加して、 out_rec を手繰れるようにしてみました

あと、フィルタ関連の関数で out_rec 構造体のサンプリング周期のメンバ smp_t だけを必要とするため、out_rec 構造体を引数に引き回してたので、 smp_t だけを引き回すようにしてみました

プログラム

prog25.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
#define ERR(s) do{ fprintf(stderr, "ERR "); MSG(s); exit(1); }while(0)

int bk_buf = -1;

int
rd(void)
{
	int v;

	if((v = bk_buf) < 0) return getchar();
	bk_buf = -1;
	return v;
}

void
bk(int v)
{
	if(bk_buf >= 0) ERR("give up");
	bk_buf = v;
}

void
rd_str(int n, char *s)
{
	int i;

	for(i=0; i<n; i++) s[i] = rd();
	s[i] = '\0';
}

int
rd_str_chk(int n, char *s)
{
	char buf[256];

	rd_str(n, buf);
	return strcmp(buf, s) == 0;
}

int
rd_int(int n) /* big endian */
{
	int i, v;

	v = 0;
	for(i=0; i<n; i++) v = (v << 8) | rd();
	return v;
}

int
rd_delta(void)
{
	int v, c;

	v = 0;
	do{
		if((c = rd()) == EOF) return EOF;
		v = (v << 7) | (c & 0x7f);
	}while(c & 0x80);
	return v;
}


int
opt_idx(char *key, int ac, char **av)
{
	int i;

	for(i=1; i<ac; i++) if(strcmp(av[i], key) == 0) return i;
	return -1;
}

char *
opt_str(char *key, int ac, char **av, char *def)
{
	int i;

	i = opt_idx(key, ac, av);
	if(i < 0 || i+1 >= ac) return def;
	return av[i+1];
}

int
opt_int(char *key, int ac, char **av, int def)
{
	char *s;

	if((s = opt_str(key, ac, av, NULL)) == NULL) return def;
	return strtol(s, NULL, 0);
}

struct out_rec{
	int smp_freq, smp_cnt;
	int bit_len;
	int sign;
	int ch_num;
	FILE *fp;

	int base;
	int amp;
	int iv_min;
	int iv_max;
	double smp_t, sec;
};

int
sox_version(void)
{
	FILE *fp;
	int v1, v2, v3;

	if((fp = popen("sox --version | sed -e 's/^.*v//'", "r")) == NULL) return -1;
	fscanf(fp, "%d.%d.%d", &v1, &v2, &v3);
	pclose(fp);
	return v1*10000 + v2*100 + v3;
}

char *
sox_bit_len_fmt(int bit_len)
{
	return sox_version() >= 140400 ?
		(bit_len == 8 ? "-b 8" : "-b 16") :
		(bit_len == 8 ? "-b" : "-w");
}

void
out_init(struct out_rec *ot, int ac, char **av)
{
	char cmd[1024], *fn;

	ot->smp_freq = opt_int("-r", ac, av, 44100);
	ot->smp_cnt = 0;
	ot->bit_len = opt_int("-b", ac, av, 16);
	if(ot->bit_len != 8 && ot->bit_len != 16) ERR("bit_len");
	ot->sign = (opt_idx("-u", ac, av) < 0);
	ot->ch_num = opt_int("-c", ac, av, 2);

	ot->fp = stdout;
	cmd[0] = '\0';
	fn = "";
	if(opt_idx("-play", ac, av) >= 0){
		strcat(cmd, "play");
	}else if(opt_idx("-sox", ac, av) >= 0){
		strcat(cmd, "sox");
		fn = opt_str("-sox", ac, av, "-d");
	}
	if(cmd[0]){
		sprintf(cmd + strlen(cmd), " -t raw -r %d %s %s -c %d - %s",
			ot->smp_freq, sox_bit_len_fmt(ot->bit_len),
			ot->sign ? "-s" : "-u",
			ot->ch_num, fn);
		if((ot->fp = popen(cmd, "w")) == NULL) ERR("popen");
	}
	ot->amp = ot->bit_len == 8 ? 127 : 32767;
	ot->base = ot->sign ? 0 : ot->amp + 1;
	ot->iv_min = ot->base - (ot->amp + 1);
	ot->iv_max = ot->base + ot->amp;

	ot->smp_t = 1.0 / ot->smp_freq;
}

void
out_do(struct out_rec *ot, double v)
{
	int iv;

	iv = ot->base + (int)(v * ot->amp);
	if(iv > ot->iv_max) iv = ot->iv_max;
	if(iv < ot->iv_min) iv = ot->iv_min;
	switch(ot->bit_len){
	case 8:
		fputc(iv, ot->fp);
		break;
	case 16:
		fputc(iv & 0xff, ot->fp);
		fputc((iv >> 8) & 0xff, ot->fp);
		break;
	}
}

#define OFF		0
#define ON		1

#define LPF		1
#define HPF		2
#define BPF		3

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

struct filter_stat_rec{
	int idx;
	double in[4], out[4];
	double a0, a1, a2, b0, b1, b2, div_a0;
};

void
filter_update(struct filter_rec *fl, struct filter_stat_rec *stat, double smp_t)
{
	double w0, alpha, cos_w0;

	if(fl->type == OFF) return;

	w0 = 2 * M_PI * fl->freq * smp_t;
	cos_w0 = cos(w0);
	alpha = sin(w0) / (2 * fl->Q);

	switch(fl->type){
	case LPF:
		stat->b1 = 1 - cos_w0;
		stat->b0 = stat->b2 = stat->b1 * 0.5;
		break;
	case HPF:
		stat->b1 = -(1 + cos_w0);
		stat->b0 = stat->b2 = -stat->b1 * 0.5;
		break;
	case BPF:
		stat->b0 = fl->Q * alpha;
		stat->b1 = 0;
		stat->b2 = -stat->b0;
		break;
	}
	stat->a0 = 1 + alpha;
	stat->a1 = -2 * cos_w0;
	stat->a2 = 1 - alpha;
	if(stat->a0 != 0) stat->div_a0 = 1 / stat->a0;
}

void
filter_init(struct filter_rec *fl, struct filter_stat_rec *stat, double smp_t)
{
	int i;

	for(i=0; i<4; i++) stat->in[i] = stat->out[i] = 0;
	stat->idx = 0;

	filter_update(fl, stat, smp_t);
}

double
filter_out(struct filter_stat_rec *stat, double in)
{
	double *out_p;
	int i_1, i_2;

	stat->in[stat->idx] = in;
	out_p = &stat->out[stat->idx];

	if(stat->a0 != 0){
		i_1 = (stat->idx + 4 - 1) & 3;
		i_2 = (i_1 + 4 - 1) & 3;
		*out_p = (stat->b0 * in
			+ stat->b1 * stat->in[i_1]
			+ stat->b2 * stat->in[i_2]
			- stat->a1 * stat->out[i_1]
			- stat->a2 * stat->out[i_2] ) * stat->div_a0;
	}else{
		*out_p = 0;
	}
	stat->idx = (stat->idx + 1) & 3;
	return *out_p;
}


#define NOTE_BUF_N	256
#define STAT_BUF_N	256

struct vco_stat_rec{
	double cycle, freq, sec;
};

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

#define DELAY_BUF_N	(44100 * 5 / 10) /* max 0.5 sec */

struct note_rec;
struct tone_compo_rec;

struct stat_rec{
	struct vco_stat_rec vco[2];
	struct filter_stat_rec filter[2];
	double off_v;
	int fst_smp_cnt;
	double delay_buf[2][ DELAY_BUF_N ];

	struct note_rec *nt;
	struct tone_compo_rec *tone_compo;
	struct stat_rec *next;
} stat_buf[ STAT_BUF_N ];

void
stat_init(struct stat_rec *stat, double sec)
{
	int i;
	struct vco_stat_rec *vco;

	for(i=0; i<2; i++){
		vco = &stat->vco[i];
		vco->cycle = 0;
		vco->sec = sec;
	}
	for(i=0; i<2; i++){
		stat->filter[i].idx = -1;
	}
	stat->fst_smp_cnt = -1;
}

void
stat_buf_init(void)
{
	int i;

	for(i=0; i<STAT_BUF_N; i++) stat_buf[i].nt = NULL;
}

struct note_rec{
	int ch;
	int note;
	int onoff;
	double on_sec;
	double off_sec;
	int velo;

	struct out_rec *ot;
	struct stat_rec *stat_lst;
} note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
	return note_buf[i].ch < 0;
}

void
note_buf_free(int i)
{
	note_buf[i].ch = -1;
}

void
note_buf_init(void)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
}

int
note_buf_search(int ch, int note, int onoff)
{
	int i;

	for(i=0; i<NOTE_BUF_N; i++) {
		if( (ch < 0 && note_buf[i].ch < 0) ||
		    (note_buf[i].ch == ch &&
		     note_buf[i].note == note &&
		     note_buf[i].onoff == onoff) ) return i;
	}
	return -1;
}

int
header(void)
{
	int v;

	if(!rd_str_chk(4, "MThd")) ERR("header id");
	if(rd_int(4) != 6) ERR("header size");
	if(rd_int(2) != 0) ERR("header format type");
	if(rd_int(2) != 1) ERR("header track num");
	if((v = rd_int(2)) & 0x8000) ERR("header division");
	return v; /* div4 */
}

double
note_to_freq(int note)
{
	return 440 * pow(2, (note - 69) / 12.0);
}

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

struct modu_rec{
	int pitch1, pitch2, filter1, filter2;
};

struct lfo_rec{
	int wave;
	double freq, delay;
};

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

struct delay_rec{
	int onoff;
	double sec, gain;
};

struct tone_rec{
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
} tone_inf[] = {
	{	/* strings */
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
		{ 0.15, 0.5, 0.8, 0.5 },
		1.0,
		{ OFF, 12, OFF, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* bass */
		{ WAVE_SAW, WAVE_SQUARE, OFF, 0.5, OFF },
		{ LPF, 1000, 1.5 }, { OFF, },
		{ 0.05, 0.2, 0.4, 0.2 },
		1.0,
		{ OFF, 60, OFF, OFF }, { WAVE_SIN, 1.5, 0.2 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, ON,
	},{	/* lead */
		{ WAVE_SQUARE, WAVE_SQUARE, 10, 0.5, OFF },
		{ LPF, 2000, 4 }, { OFF, },
		{ 0.01, 0.2, 0.8, 0.3 },
		1.0,
		{ OFF, 1, 5, OFF }, { WAVE_SIN, 4, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ ON, 0.2, 0.4}, OFF,
	},{	/* SIN */
		{ WAVE_SIN, WAVE_SIN, OFF, 0, OFF },
		{ LPF, 20000, 0.8 }, { OFF, },
		{ 0, 0.3, 0.2, 0.3 },
		1.4,
		{ 25, OFF, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
}, drum_tone_inf[] = {
	{	/* bass */
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ LPF, 400, 1 }, { OFF, },
		{ 0.01, 0.18, 0, 0.18 },
		9.0,
		{ 100, OFF, 30, OFF }, { WAVE_NOISE, 0, 0.5 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* snare */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.8, ON },
		{ LPF, 1500, 1.7 }, { OFF, },
		{ 0, 0.4, 0.3, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* tom */
		{ WAVE_SQUARE, WAVE_NOISE, OFF, 0.5, ON },
		{ LPF, 800, 2.5 }, { OFF, },
		{ 0, 0.03, 0.1, 0.4 },
		5,
		{ OFF, OFF, OFF, OFF }, {},
		{ 200, OFF, 1200, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat close */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0.15, 0, 0.15 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* hi-hat open */
		{ WAVE_SQUARE, WAVE_SQUARE, 700, 0.9, ON },
		{ LPF, 16000, 2 }, { OFF, },
		{ 0, 0, 1, 0.35 },
		0.6,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{	/* cymbal */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.8, ON },
		{ LPF, 4000, 2 }, { OFF, },
		{ 0, 0.01, 0.2, 0.8 },
		9,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, 600, OFF },
		{ OFF, }, OFF,
	},{	/* side stick */
		{ WAVE_NOISE, WAVE_SQUARE, OFF, 0.2, OFF },
		{ LPF, 1700, 3 }, { OFF, },
		{ 0, 0.012, 0.1, 0.7 },
		10,
		{ OFF, OFF, OFF, OFF }, {},
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	}
};

struct tone_compo_rec{
	struct tone_rec *tone;
	int note; /* -1 : event note */
	double rate;
};

#define PROG_DRUM	0

struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst[] = {
	{
		PROG_DRUM, 36, /* bass drum1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[0], 28, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 37, /* side stick */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[6], 110, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 40, /* electric snare */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[1], 69, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 41, /* low floor tom */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[2], 50, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 42, /* closed hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[3], 115 + 8, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 46, /* open hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[4], 115 + 8, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 49, /* crash cymbal 1 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[5], 75, 1.0 }, { NULL, }
		}
	},{
		PROG_DRUM, 57, /* crash cymbal 2 */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[5], 85, 1.0 }, { NULL, }
		}
	},{
		48, -1, /* timpani */
		(struct tone_compo_rec []){
			{ &tone_inf[0], -1, 1.0 }, { NULL, }
		}
	},{
		50, -1, /* strings ensamble 2 */
		(struct tone_compo_rec []){
			{ &tone_inf[0], -1, 1.0 }, { NULL, }
		}
	},{
		35, -1, /* electric bass (pick) */
		(struct tone_compo_rec []){
			{ &tone_inf[1], -1, 1.0 }, { NULL, }
		}
	},{
		79, -1, /* whistle */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		81, -1, /* lead 1 (square) */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		87, -1, /* lead 7 (fifths) */
		(struct tone_compo_rec []){
			{ &tone_inf[2], -1, 1.0 }, { NULL, }
		}
	},{
		24, -1, /* tango accordion */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		67, -1, /* tenor sax */
		(struct tone_compo_rec []){
			{ &tone_inf[3], -1, 1.0 }, { NULL, }
		}
	},{
		-1, /* tail */
	}
};

void
stat_lst_free(struct stat_rec *stat)
{
	if(!stat) return;
	stat->nt = NULL;
	stat_lst_free(stat->next);
}

struct stat_rec *
stat_lst_alloc_init(int n, double sec, struct note_rec *nt, struct tone_compo_rec *tone_compo_arr, struct stat_rec *next)
{
	struct stat_rec *stat;
	int i;

	if(n > 1){
		next = stat_lst_alloc_init(n - 1, sec, nt, tone_compo_arr + 1, next);
		if(next == NULL) return NULL;
	}
	for(i=0; i<STAT_BUF_N; i++) if(stat_buf[i].nt == NULL) break;
	if(i >= STAT_BUF_N){
		stat_lst_free(next);
		return NULL;
	}
	stat = &stat_buf[i];
	stat->nt = nt;
	stat->tone_compo = tone_compo_arr;
	stat->next = next;
	stat_init(stat, sec);
	return stat;
}

#define MIDI_CH_N	16
struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
} ch_inf[ MIDI_CH_N ];

#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

struct tone_compo_rec *
tone_compo_get(int ch, int note, int *ret_n)
{
	struct tone_compo_rec *tone_compo;
	int prog, i;

	prog = CH_PROG(ch);
	for(i=0; tones_lst[i].prog >= 0; i++){
		if(tones_lst[i].prog == prog &&
		   (tones_lst[i].note == note || tones_lst[i].note < 0)) break;
	}
	if(tones_lst[i].prog < 0) return NULL; /* not found */
	tone_compo = tones_lst[i].tone_compo;
	if(ret_n){
		for(i=0; tone_compo[i].tone; i++);
		*ret_n = i;
	}
	return tone_compo;
}

double
env_out(struct stat_rec *stat, double sec)
{
	struct note_rec *nt;
	struct env_rec *e;
	double v;

	nt = stat->nt;
	e = &stat->tone_compo->tone->env;
	if(nt->onoff){
		sec -= nt->on_sec;
		if(sec < e->attack){
			v = sec / e->attack;
			return log10(1 + v * 9);
		}
		sec -= e->attack;
		if(sec < e->decay){
			v = sec / e->decay;
			v = log10(1 + v * 9);
			return 1 - v * (1 - e->sustain);
		}
		return e->sustain;
	}
	sec -= nt->off_sec;
	if(sec < e->release){
		v = sec / e->release;
		v = log10(1 + v * 9);
		return (1 - v) * stat->off_v;
	}
	return 0;
}

void
modu_out(double v, struct modu_rec *modu, double *ret_arr)
{
	if(modu->pitch1) ret_arr[0] += v * modu->pitch1 * (1.0 / 1200);
	if(modu->pitch2) ret_arr[1] += v * modu->pitch2 * (1.0 / 1200);
	if(modu->filter1) ret_arr[2] += v * modu->filter1 * (1.0 / 1200);
	if(modu->filter2) ret_arr[3] += v * modu->filter2 * (1.0 / 1200);
}

double
lfo_out(struct lfo_rec *lfo, struct modu_rec *modu, double sec, double on_sec)
{
	double dsec;

	if(!(modu->pitch1 || modu->pitch2 || modu->filter1 || modu->filter2)) return 0;
	if((dsec = sec - on_sec - lfo->delay) <= 0) return 0;
	return wave_out(lfo->wave, lfo->freq * dsec);
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

double
vcf_out(struct filter_rec *fl, struct filter_stat_rec *stat, double smp_t, double modu_v, double v)
{
	struct filter_rec tmp;

	if(stat->idx < 0) filter_init(fl, stat, smp_t);
	if(modu_v != 0){
		tmp = *fl;
		tmp.freq *= pow(2, modu_v);
		filter_update(&tmp, stat, smp_t);
	}
	return filter_out(stat, v);
}

void
delay_buf_set(struct stat_rec *stat, double v, double env_v)
{
	int i;

	i = (stat->nt->ot->smp_cnt - stat->fst_smp_cnt) % DELAY_BUF_N;
	stat->delay_buf[0][i] = v;
	stat->delay_buf[1][i] = env_v;
}

#define MIX(a, b, rate)		( (a) * (1 - (rate)) + (b) * (rate) )

double
tbl_lookup(double *tbl, int n, double d_idx)
{
	int i;

	i = (int)d_idx;
	d_idx -= i;
	i %= n;
	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
}

double
delay_buf_lookup(struct stat_rec *stat, double sec, double *ret_env_v)
{
	double cycle;

	cycle = stat->nt->ot->smp_freq * sec - stat->fst_smp_cnt;
	if(ret_env_v) *ret_env_v = tbl_lookup(stat->delay_buf[1], DELAY_BUF_N, cycle);
	return tbl_lookup(stat->delay_buf[0], DELAY_BUF_N, cycle);
}

double
delay_out(struct stat_rec *stat, struct delay_rec *delay, double *ret_env_v)
{
	double v, sec;

	sec = stat->nt->ot->sec;
	if(sec - stat->nt->on_sec < delay->sec) return 0;
	v = delay_buf_lookup(stat, sec - delay->sec, ret_env_v) * delay->gain;
	if(ret_env_v) *ret_env_v *= delay->gain;
	return v;
}

double
chorus_out(struct stat_rec *stat)
{
	double sec,dsec, bak_sec;

	sec = stat->nt->ot->sec;
	dsec = sec - stat->nt->on_sec;
	bak_sec = 0.01 + 0.001 * sin(2 * M_PI * 0.8 * dsec);
	if(dsec < bak_sec) return 0;
	return delay_buf_lookup(stat, sec - bak_sec, NULL);
}

double
tone_out(struct stat_rec *stat, double freq, double env_v)
{
	struct tone_rec *tn;
	int i;
	double v, v1, v2, lfo_v, modu_v[4];
	struct vco_rec *vco;
	struct out_rec *ot;

	tn = stat->tone_compo->tone;
	ot = stat->nt->ot;

	lfo_v = lfo_out(&tn->lfo, &tn->lfo_modu, ot->sec, stat->nt->on_sec);

	for(i=0; i<4; i++) modu_v[i] = 0;
	modu_out(lfo_v, &tn->lfo_modu, modu_v);
	modu_out(env_v, &tn->env_modu, modu_v);

	vco = &tn->vco;
	v1 = vco_out(vco->wave1, &stat->vco[0], freq, ot->sec, modu_v[0]);

	modu_v[1] += vco->tune * (1.0 / 1200);
	v2 = vco_out(vco->wave2, &stat->vco[1], freq, ot->sec, modu_v[1]);
	if(vco->ring){
		v = v1 * v2;
		v1 = (v1 + v2) * 0.5;
		v2 = v;
	}
	v = MIX(v1, v2, vco->mix);

	if(tn->fl1.type != OFF) v = vcf_out(&tn->fl1, &stat->filter[0], ot->smp_t, modu_v[2], v);
	if(tn->fl2.type != OFF) v = vcf_out(&tn->fl2, &stat->filter[1], ot->smp_t, modu_v[3], v);

	v *= env_v * tn->level;
	return v;
}

int
note_out(struct note_rec *nt, double *vl, double *vr)
{
	double v, sum_v, freq, pan, env_v, d_env_v;
	int nch, cnt;
	struct tone_rec *tn;
	struct tone_compo_rec *tone_compo;
	struct stat_rec *stat;
	struct out_rec *ot;

	nch = nt->ch;
	ot = nt->ot;
	cnt = 0;
	sum_v = 0;
	for(stat=nt->stat_lst; stat; stat=stat->next){
		tone_compo = stat->tone_compo;
		if(tone_compo == NULL) continue;
		cnt++;
		tn = tone_compo->tone;
		freq = note_to_freq(tone_compo->note >= 0 ? tone_compo->note : nt->note);

		if(stat->fst_smp_cnt < 0) stat->fst_smp_cnt = ot->smp_cnt;
		if(ch_inf[nch].bend != 0) freq *= pow(2, ch_inf[nch].bend * ch_inf[nch].bend_range * (1.0 / (8192 * 12)));

		env_v = env_out(stat, ot->sec);
		v = (env_v > 0) ? tone_out(stat, freq, env_v) : 0;

		d_env_v = 0;
		if(tn->delay.onoff) v += delay_out(stat, &tn->delay, &d_env_v);
		d_env_v += env_v;
		delay_buf_set(stat, v, d_env_v);
		if(tn->chorus) v += chorus_out(stat);

		sum_v += v * tone_compo->rate;

		if(nt->onoff == 0 && ot->sec - nt->off_sec >= tn->env.release && (!tn->delay.onoff || d_env_v < 0.01)){
			stat->tone_compo = NULL;
		}
	}
	v = sum_v;

	v *= (nt->velo * (1.0 / 127)) * (ch_inf[nch].vol * (1.0 / ((1<<14)-1)));
	if(ot->ch_num == 1){
		*vl += v;
	}else{
		pan = ch_inf[nch].pan * (1.0 / (1<<14));
		*vl += (1 - pan) * v;
		*vr += pan * v;
	}
	return cnt == 0;
}

void
data_out(struct out_rec *ot, double evt_sec)
{
	double vl, vr;
	int i;
	struct note_rec *nt;

	while((ot->sec = ot->smp_t * ot->smp_cnt) < evt_sec){
		vl = vr = 0;
		for(i=0; i<NOTE_BUF_N; i++){
			if(note_buf_is_free(i)) continue;
			nt = &note_buf[i];
			nt->ot = ot;
			if(note_out(nt, &vl, &vr)){
				stat_lst_free(nt->stat_lst);
				note_buf_free(i);
			}
		}
		out_do(ot, vl * (1.0 / 16));
		if(ot->ch_num > 1) out_do(ot, vr * (1.0 / 16));
		ot->smp_cnt++;
	}
}

void
note_onoff(int onoff, double evt_sec, int ch, int note, int velo)
{
	int i, tone_n;
	struct note_rec *nt;
	struct stat_rec *stat;
	struct tone_compo_rec *tone_compo;

	if(onoff){
		if((i = note_buf_search(-1, -1, -1)) < 0){
			MSG("note_buf full");
			return;
		}
		nt = &note_buf[i];
		nt->note = note;
		nt->ch = ch;
		nt->onoff = 1;
		nt->on_sec = evt_sec;
		nt->velo = velo;

		if((tone_compo = tone_compo_get(ch, note, &tone_n)) == NULL){
			note_buf_free(i);
			return;
		}
		if((nt->stat_lst = stat_lst_alloc_init(tone_n, evt_sec, nt, tone_compo, NULL)) == NULL){
			note_buf_free(i);
		}
	}else{
		if((i = note_buf_search(ch, note, 1)) < 0) return;
		nt = &note_buf[i];
		for(stat=nt->stat_lst; stat; stat=stat->next){
			stat->off_v = env_out(stat, evt_sec);
		}
		nt->onoff = 0;
		nt->off_sec = evt_sec;
	}
}

void
msb_set(int *vp, int v)
{
	*vp &= ~(127<<7);
	*vp |= v<<7;
}

void
lsb_set(int *vp, int v)
{
	*vp &= ~127;
	*vp |= v;
}

void
bend_note_update(int ch, double sec)
{
	int i, j;
	struct note_rec *nt;
	struct vco_stat_rec *vco_stat;
	struct stat_rec *stat;

	for(i=0; i<NOTE_BUF_N; i++){
		if(note_buf_is_free(i)) continue;
		nt = &note_buf[i];
		if(nt->ch != ch) continue;
		for(stat=nt->stat_lst; stat; stat=stat->next){
			for(j=0; j<2; j++){
				vco_stat = &stat->vco[j];
				vco_stat_update(vco_stat, vco_stat->freq, sec);
			}
		}
	}
}

int
main(int ac, char **av)
{
	int div4, delta_sum;
	int i, n, v, hi, low, note, velo;
	int ch, type;
	double sec;
	struct out_rec otr;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	out_init(&otr, ac, av);
	note_buf_init();
	stat_buf_init();
	for(i=0; i<MIDI_CH_N; i++){
		ch_inf[i].vol = 0;
		ch_inf[i].pan = 1<<(14-1);
		ch_inf[i].bend = 0;
		ch_inf[i].rpn = 0;
		ch_inf[i].bend_range = 2;
		ch_inf[i].prog =-1;
	}
	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;

		if((v = rd()) & 0x80){
			hi = (v >> 4) & 0xf;
			low  = v & 0xf;
		}else bk(v);

		data_out(&otr, sec);

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;
			case 39: /* channel volume lsb */
				lsb_set(&ch_inf[ch].vol, v);
				break;
			case 10: /* pan msb */
				msb_set(&ch_inf[ch].pan, v);
				break;
			case 42: /* pan lsb */
				lsb_set(&ch_inf[ch].pan, v);
				break;
			case 100: /* rpn lsb */
				lsb_set(&ch_inf[ch].rpn, v);
				break;
			case 101: /* rpn msb */
				msb_set(&ch_inf[ch].rpn, v);
				break;
			case 6: /* data entry msb */
				switch(ch_inf[ch].rpn){
				case 0: /* pitch bend range */
					ch_inf[ch].bend_range = v;
					break;
				}
				break;
			}
			break;
		case 0xa:
			rd();
			rd();
			break;
		case 0xe: /* pitch wheel change */
			bend_note_update(ch, sec);
			v = rd();
			v |= rd() << 7;
			ch_inf[ch].bend = v - 8192;
			break;
		case 0xc: /* program number */
			v = rd();
			ch_inf[ch].prog = v;
			break;
		case 0xd:
			rd();
			break;
		case 0xf:
			type = rd();
			switch(low){
			case 0:
				while(rd() != 0xf7);
				break;
			case 1:
			case 3:
				rd();
				break;
			case 2:
				rd();
				rd();
				break;
			case 0xf: /* meta */
				n = rd();
				switch(type){
				case 0x51: /* set tempo */
					v = rd_int(n);
					tempo = v;
					tempo_delta_sum = delta_sum;
					tempo_sec = sec;
					break;
				default:
					for(i=0; i<n; i++) rd();
					break;
				}
				break;
			default:
				break;
			}
			break;
		}
	}
	if(otr.fp != stdout) pclose(otr.fp);
	return 0;
}

/* EOF */

解説

$ gcc -o prog24 prog24.c -lm
$ gcc -o prog25 prog25.c -lm
$ cat L3007_06.MID | ./prog24 > prog24.raw
$ cat L3007_06.MID | ./prog25 > prog25.raw
$ cmp prog24.raw prog25.raw
$

ソース・コードをいじりましたが、出力結果は同一です

それでは、本当に複数の音色が鳴るか確認してみます

ソース・コードの tones_lst[] のデータを少しだけ変えて試してみます

$ cp prog25.c prog26.c

prog26.c を編集

struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst[] = {
	{
  :
	},{
		PROG_DRUM, 41, /* low floor tom */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[2], 50, 1.0 }, { NULL, }
		}
	},{
  :
	},{
		PROG_DRUM, 46, /* open hi-hat */
		(struct tone_compo_rec []){
			{ &drum_tone_inf[2], 50, 1.0 }, // ここの一行を挿入 !!!
			{ &drum_tone_inf[4], 115 + 8, 1.0 }, { NULL, }
		}
	},{
  :

これで、オープン・ハイハットが鳴るタイミングで、ロー・タムらしき音も鳴るはずです

$ gcc -o prog26 prog26.c -lm
$ cat L3007_06.MID | ./prog26 -sox prog26.wav
$ play prog26.wav

可能なら

$ cat L3007_06.MID | ./prog26 -play

prog26_xaa.mp3

prog26_xab.mp3

確かにロー・タム聴こえてます。オッケーです (^_^)v

ここらで処理速度の確認を

まず以前、最後に計ったのは、prog20 だったので、その時の記録です

$ gcc -o prog20 prog20.c -lm
$ time cat L3007_06.MID | ./prog20 > xxx.raw

real 9m56.995s
user 9m54.365s
sys 0m1.620s
$

では prog25 と、オープン・ハイハットの音色を2つにした prog26 を計ってみます

$ gcc -o prog25 prog25.c -lm
$ gcc -o prog26 prog26.c -lm
$ time cat L3007_06.MID | ./prog25 > xxx.raw

real    9m29.610s
user    9m26.575s
sys     0m1.296s
$
$ time cat L3007_06.MID | ./prog26 > xxx.raw

real    9m34.179s
user    9m31.748s
sys     0m1.308s
$

機能を追加してますが、同じレベルですね。

ソース・コードを整理したせいか若干速度が上がってます。

prog25 と prog26 では、音色一つ分だけ、重くなったようです


ソース・コードの分割

ここまでプログラムのソース・コードは1つのファイルのままで、 ひっぱってきました

$ wc -l prog25.c
1166 prog25.c

1166行 ...

今さらですが prog25.c を元にして、ソース・コードを機能単位で分割し、 整理してみます

とりあえず振り分け

とりあえず prog25.c の先頭から順に、 ザクザクと切り刻んで、振り分けていってみます

種別 名前 移動先
include行 stdio.h util.h
stdlib.h util.h
string.h util.h
math.h util.h
マクロ MSG util.h
ERR util.h
変数 bk_buf rd.c
関数 rd rd.c
bk rd.c
rd_str rd.c
rd_str_chk rd.c
rd_int rd.c
rd_delta rd.c
関数 opt_idx util.c
opt_str util.c
opt_int util.c
構造体 out_rec out.h
関数 sox_version out.c
sox_bit_len_fmt out.c
out_init out.c
out_do out.c
マクロ OFF util.h
ON util.h
マクロ LPF filter.h
HPF filter.h
BPF filter.h
構造体 filter_rec filter.h
filter_stat_rec filter.h
関数 filter_update filter.c
filter_init filter.c
filter_out filter.c
マクロ NOTE_BUF_N note.h
マクロ STAT_BUF_N stat.h
構造体 vco_stat_rec vco.h
関数 vco_stat_update vco.c
マクロ DELAY_BUF_N delay.h
構造体 stat_rec stat.h
関数 stat_init stat.c
stat_buf_init stat.c
構造体 note_rec note.h
変数 note_buf note.c
関数 note_buf_is_free note.c
note_buf_free note.c
note_buf_init note.c
note_buf_search note.c
関数 header main.c
関数 note_to_freq note.c
マクロ WAVE_SIN wave.h
WAVE_SAW wave.h
WAVE_SQUARE wave.h
WAVE_NOISE wave.h
関数 wave_out wave.c
構造体 vco_rec vco.h
構造体 modu_rec modu.h
構造体 lfo_rec lfo.h
構造体 env_rec env.h
構造体 delay_rec delay.h
構造体 tone_rec tone.h
変数 tone_inf tone.c
drun_tone_inf tone.c
構造体 tone_compo_rec tone.h
マクロ PROG_DRUM tone.c
変数 tones_lst tone.c
関数 stat_lst_free stat.c
stat_lst_alloc_init stat.c
マクロ MIDI_CH_N ch.h
構造体 ch_rec ch.h
変数 ch_inf ch.c
マクロ CH_PROG tone.c
関数 tone_compo_get tone.c
関数 env_out env.c
関数 modu_out modu.c
関数 lfo_out lfo.c
関数 vco_out vco.c
関数 vcf_out vcf.c
関数 delay_buf_set delay.c
マクロ MIX util.h
関数 tbl_lookup delay.c
delay_buf_lookup delay.c
delay_out delay.c
chorus_out delay.c
関数 tone.out tone.c
関数 note_out note.c
関数 data_out main.c
関数 note_onoff note.c
関数 msb_set ch.c
lsb_set ch.c
関数 bend_note_update main.c
main main.c

構造体の定義と変数宣言を同時にしてる箇所が、何箇所かあります

struct foo{
  :
} bar;

のような箇所です

この場合は、構造体の定義部分を .hファイルに、 変数の宣言部分を .cファイルに振り分けるようにします

--> .hファイル
struct foo{
  :
};
<-- .hファイル
--> .cファイル
struct foo bar;
<-- .cファイル

これで、なんとなく機能別に振り分けれた感じです

名前 機能
main メイン処理
rd データの読み込み
ch MIDIチャンネル毎のパラメータ管理
note 鍵盤ON/OFFイベント関連
tone 音色関連
vco VCO処理
wave 波形生成
vcf VCF処理
modu モジュレーション
filter フィルタ
lfo LFO処理
env エンベロープ
delay ディレイ・エフェクト
out 波形出力
stat 各種状態の管理
util 各種ユーティリテイ

ヘッダファイルを整える

次にヘッダファイルを整えてみます

例えば ch.h は、次のように整えてみます

--> ch.h
#ifndef __CH_H__	// 二重include防止のため追加
#define __CH_H__	// 二重include防止のため追加

#define MIDI_CH_N	16

struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
};

extern struct ch_rec ch_inf[ MIDI_CH_N ]; // 変数のextern宣言追加

void msb_set(int *vp, int v);	// 関数のプロトタイプ宣言追加
void lsb_set(int *vp, int v);	// 関数のプロトタイプ宣言追加

#endif			// 二重include防止のため追加
<-- ch.h

他のモジュールのヘッダファイルも同様に追記します

ヘッダファイルの無いモジュールは空の内容で作成して、追記します

tone.c の 変数 tones_lst については、名前なしの構造体ですが、 tone.c の中でしか参照してないので、tone.h に extern宣言は追加しません

Cファイルにinclude行を追加

続いて .c ファイルの先頭で 対応する .h ファイルの include 行を追加します

例えば ch.c は、次のように追加します

--> ch.c
#include "ch.h" // この行を追加

struct ch_rec ch_inf[ MIDI_CH_N ];

void
msb_set(int *vp, int v)
  :
<-- ch.c

Makefile

そして、未だ早過ぎるとは思いますが、とりあえず Makefile を作成

--> Makefile
CC = gcc -Wall
LIB = -lm
TARG = prog27
OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o

all: $(TARG)

$(TARG): $(OBJS)
	$(CC) -o $@ $(OBJS) $(LIB)

clean:
	rm -f $(TARG) $(OBJS) *~

# EOF
<-- Makefile

そして make ... って無理です (^_^;)

ヘッダファイルの include が不十分です

依存関係

それぞれのモジュールが、他のどのモジュールを使っているか、 依存関係を調べてみます。

各ヘッダファイルを見て、 他のモジュールで定義してる構造体を参照していたら、 そのモジュールを使っているとして、まとめてみます

名前 機能 使用してるもの
main メイン処理 (ヘッダファイルなし) 本当は全部
rd データの読み込み
ch MIDIチャンネル毎のパラメータ管理
note 鍵盤ON/OFFイベント関連の stat, out
tone 音色関連 vco, filter, env, modu, lfo, delay
vco VCO処理 wave
wave 波形生成
vcf VCF処理 filter
modu モジュレーション
filter フィルタ
lfo LFO処理 modu
env エンベロープ stat
delay ディレイ・エフェクト stat
out 波形出力
stat 各種状態の管理 vco, filter, note, tone
util 各種ユーティリテイ

お試しコンパイル

依存関係の無さそうなところから、コンパイルを試してみます

$ make util.o
gcc -Wall    -c -o util.o util.c
$

エラーも警告もなし (^_^)

$ make rd.o
gcc -Wall    -c -o rd.o rd.c
rd.c: In function 'rd':
rd.c:10:2: warning: implicit declaration of function 'getchar' [-Wimplicit-function-declaration]
rd.c: In function 'bk':
rd.c:18:2: warning: implicit declaration of function 'ERR' [-Wimplicit-function-declaration]
rd.c: In function 'rd_str_chk':
rd.c:37:2: warning: implicit declaration of function 'strcmp' [-Wimplicit-function-declaration]
rd.c: In function 'rd_delta':
rd.c:57:20: error: 'EOF' undeclared (first use in this function)
rd.c:57:20: note: each undeclared identifier is reported only once for each function it appears in
make: *** [rd.o] Error 1
$

早速ダメ (T_T)

stdio.h をインクルードしてないのと、 util.h で定義してるマクロERRを参照してるためです

util.h で stdio.h もインクルードしてるので、 rd.c に util.h のinclue行を追加すれば良さそうです

が、... 将来 rd.c の内容を inline 関数にして rd.h に移動したりするかも知れないし、 しないかも知れない ....

ということで、rd.h に util.h のinclue行を追加しておきます。 (邪道な気もしますが...;-p)

--> rd.h
#ifndef __RD_H__
#define __RD_H__

#include "util.h"

extern int bk_buf;

int rd(void);
void bk(int v);
void rd_str(int n, char *s);
int rd_str_chk(int n, char *s);
int rd_int(int n);
int rd_delta(void);

#endif
<-- rd.h
$ make rd.o
gcc -Wall    -c -o rd.o rd.c
$

成功 (^_^)

$ make ch.o
gcc -Wall    -c -o ch.o ch.c
$
$ make wave.o
gcc -Wall    -c -o wave.o wave.c
wave.c: In function 'wave_out':
wave.c:10:3: warning: implicit declaration of function 'sin' [-Wimplicit-function-declaration]
wave.c:10:10: warning: incompatible implicit declaration of built-in function 'sin' [enabled by default]
wave.c:10:18: error: 'M_PI' undeclared (first use in this function)
wave.c:10:18: note: each undeclared identifier is reported only once for each function it appears in
wave.c:18:3: warning: implicit declaration of function 'rand' [-Wimplicit-function-declaration]
wave.c:18:28: error: 'RAND_MAX' undeclared (first use in this function)
make: *** [wave.o] Error 1
$

これは math.h のインクルードが足りてないようです

wave.h に util.h の include 行を追加...

$ make wave.o
gcc -Wall    -c -o wave.o wave.c

で、とおりました

$ make modu.o
gcc -Wall    -c -o modu.o modu.c
$
$ make filter.o
gcc -Wall    -c -o filter.o filter.c
filter.c: In function 'filter_update':
filter.c:8:17: error: 'OFF' undeclared (first use in this function)
filter.c:8:17: note: each undeclared identifier is reported only once for each function it appears in
filter.c:10:11: error: 'M_PI' undeclared (first use in this function)
filter.c:11:2: warning: implicit declaration of function 'cos' [-Wimplicit-function-declaration]
filter.c:11:11: warning: incompatible implicit declaration of built-in function 'cos' [enabled by default]
filter.c:12:2: warning: implicit declaration of function 'sin' [-Wimplicit-function-declaration]
filter.c:12:10: warning: incompatible implicit declaration of built-in function 'sin' [enabled by default]
make: *** [filter.o] Error 1

同様に filter.h に util.h の include 行を追加...

$ make filter.o
gcc -Wall    -c -o filter.o filter.c
$
$ make out.o
gcc -Wall    -c -o out.o out.c
In file included from out.c:1:0:
out.h:9:2: error: unknown type name 'FILE'
out.c: In function 'sox_version':
out.c:6:2: error: unknown type name 'FILE'
out.c:9:2: warning: implicit declaration of function 'popen' [-Wimplicit-function-declaration]
out.c:9:9: warning: assignment makes pointer from integer without a cast [enabled by default]
out.c:9:63: error: 'NULL' undeclared (first use in this function)
out.c:9:63: note: each undeclared identifier is reported only once for each function it appears in
out.c:10:2: warning: implicit declaration of function 'fscanf' [-Wimplicit-function-declaration]
out.c:10:2: warning: incompatible implicit declaration of built-in function 'fscanf' [enabled by default]
out.c:11:2: warning: implicit declaration of function 'pclose' [-Wimplicit-function-declaration]
out.c: In function 'out_init':
out.c:28:2: warning: implicit declaration of function 'opt_int' [-Wimplicit-function-declaration]
out.c:31:2: warning: implicit declaration of function 'ERR' [-Wimplicit-function-declaration]
out.c:32:2: warning: implicit declaration of function 'opt_idx' [-Wimplicit-function-declaration]
out.c:35:11: error: 'stdout' undeclared (first use in this function)
out.c:39:3: warning: implicit declaration of function 'strcat' [-Wimplicit-function-declaration]
out.c:39:3: warning: incompatible implicit declaration of built-in function 'strcat' [enabled by default]
out.c:41:3: warning: incompatible implicit declaration of built-in function 'strcat' [enabled by default]
out.c:42:3: warning: implicit declaration of function 'opt_str' [-Wimplicit-function-declaration]
out.c:42:6: warning: assignment makes pointer from integer without a cast [enabled by default]
out.c:45:3: warning: implicit declaration of function 'sprintf' [-Wimplicit-function-declaration]
out.c:45:3: warning: incompatible implicit declaration of built-in function 'sprintf' [enabled by default]
out.c:45:3: warning: implicit declaration of function 'strlen' [-Wimplicit-function-declaration]
out.c:45:17: warning: incompatible implicit declaration of built-in function 'strlen' [enabled by default]
out.c:49:14: warning: assignment makes pointer from integer without a cast [enabled by default]
out.c:49:36: error: 'NULL' undeclared (first use in this function)
out.c: In function 'out_do':
out.c:69:3: warning: implicit declaration of function 'fputc' [-Wimplicit-function-declaration]
make: *** [out.o] Error 1
$

同様に out.h に util.h の include 行を追加...

$ make out.o
gcc -Wall    -c -o out.o out.c
$

以上がutilへの依存程度で済んでる、楽な人達でした

依存の対応

次に、依存が単純そうな人達から片付けていってみます

名前 機能 使用してるもの
vco VCO処理 wave
vcf VCF処理 filter
lfo LFO処理 modu

まず vco の wave 依存あたりから

wave は既にコンパイルが通っています。 vco.h に wave.h の include行を追加します

--> vco.h
#ifndef __VCO_H__
#define __VCO_H__

#incude "wave.h"

struct vco_stat_rec{
  :
<-- vco.h
$ make vco.o
gcc -Wall    -c -o vco.o vco.c
$

OKです。

次は、vcf.h に filter.h の include行を追加

$ make vcf.o
gcc -Wall    -c -o vcf.o vcf.c
$

どんどん行きます。lfo.h に modu.h の include行を追加

$ make lfo.o
gcc -Wall    -c -o lfo.o lfo.c
lfo.c: In function 'lfo_out':
lfo.c:9:2: warning: implicit declaration of function 'wave_out' [-Wimplicit-function-declaration]
$

おっと、警告

lfo.h には現れてませんが、lfo.c からは wave を参照してるようです

lfo.c に wave.h の include行を追加するべきですが、 ここは邪道に lfo.h に wave.h の include行を追加しておきます ;-p)

--> lfo.h
#ifndef __LFO_H__
#define __LFO_H__

#include "modu.h"
#include "wave.h"

struct lfo_rec{
  :
<-- lfo.h

一旦 lfo.o を削除してリトライ

$ rm lfo.o
$ make lfo.o
gcc -Wall    -c -o lfo.o lfo.c
$

相互依存の解決

残りの人達の依存を眺めてみると...

あきらかに循環があります

名前 機能 使用してるもの
main メイン処理 (ヘッダファイルなし) 本当は全部
note 鍵盤ON/OFFイベント関連の stat, out
tone 音色関連 vco, filter, env, modu, lfo, delay
env エンベロープ stat
delay ディレイ・エフェクト stat
stat 各種状態の管理 vco, filter, note, tone

note は stat を使い、stat は note を使って相互依存してます。 さて、どうしたものか...

note.h を見ると

struct stat_rec *stat_lst;

として、stat.h で定義してる stat_rec構造体のポインタを参照

stat.h を見ると

	struct note_rec *nt;
  :
struct stat_rec *stat_lst_alloc_init(int n, double sec, struct note_rec *nt, struct tone_compo_rec *tone_compo_arr, struct stat_rec *next);

の2箇所で、note.h で定義してる note_rec構造体のポインタを参照

どちらもポインタなので、なんとかなりそうです。

参照してるのは構造体ポインタなので、この時点では構造体のサイズを知らなくても大丈夫なはずです

note.h で stat_rec構造体のポインタを参照している箇所より前方に、 本体のない stat_rec構造体の宣言だけを追加してみます

さらに通常の依存の対応として、out.h の include行も追加しておきます

--> note.h
#ifndef __NOTE_H__
#define __NOTE_H__

#define NOTE_BUF_N	256

#include "out.h" // この行を追加
struct stat_rec; // この行を追加

struct note_rec{
  :
<-- note.h

とした上で...

stat.h の方では note.h をはじめとした 依存してるモジュール vco, filter, tone の inlucde行を追加しておきます

--> stat.h
#ifndef __STAT_H__
#define __STAT_H__

#include "vco.h"
#include "filter.h"
#include "note.h"
#include "tone.h"

#define STAT_BUF_N	256

struct stat_rec{
  :
<-- stat.h

あとは、tone.h env.h delay.h に依存関係通り、include行を追加します

この状態では noteモジュール では stat_rec構造体の宣言だけで、 まだ定義がありません。 note.c の冒頭に stat.h の include行を追加します

--> note.c
#include "note.h"
#include "stat.h"

struct note_rec note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
  :
<-- note.c

さて、これで如何がか?

$ make stat.o
gcc -Wall    -c -o stat.o stat.c
$
$ make note.o
gcc -Wall    -c -o note.o note.c
note.c: In function 'note_out':
note.c:66:6: error: 'ch_inf' undeclared (first use in this function)
note.c:66:6: note: each undeclared identifier is reported only once for each function it appears in
note.c:48:6: warning: variable 'nch' set but not used [-Wunused-but-set-variable]
make: *** [note.o] Error 1

statは通りましたが、noteはダメでした... が、これは note.c で chモジュールへの参照ありですね。

note.c に ch.h の include行を追加

--> note.c
#include "note.h"
#include "stat.h"
#include "ch.h"

struct note_rec note_buf[NOTE_BUF_N];

int
note_buf_is_free(int i)
{
  :
<-- note.c
$ make note.o
gcc -Wall    -c -o note.o note.c
$

OK (^_^)

$ make env.o
gcc -Wall    -c -o env.o env.c
In file included from stat.h:7:0,
                 from env.h:4,
                 from env.c:1:
tone.h:14:17: error: field 'env' has incomplete type
make: *** [env.o] Error 1

おっと、まだ循環がありました (>_<)

表を良く見ると note と stat 以外にも

名前 機能 使用してるもの
tone 音色関連 vco, filter, env, modu, lfo, delay
env エンベロープ stat
delay ディレイ・エフェクト stat
stat 各種状態の管理 vco, filter, note, tone

tone -> env -> stat -> tone

tone -> delay -> stat -> tone

の、2つのループがあります

statを使ってる note のときと同様に、 statを使ってる env と delay について対処してみます

env.h にある stat.h の include行を削除して stat_rec構造体の宣言だけ追加 env.c に stat.h の include行を追加

同様に、delay.h にある stat.h の include行を削除して stat_rec構造体の宣言だけ追加 delay.c に stat.h の include行を追加

$ make env.o
gcc -Wall    -c -o env.o env.c
$ make delay.o
gcc -Wall    -c -o delay.o delay.c
$ make tone.o
gcc -Wall    -c -o tone.o tone.c
tone.c: In function 'tone_compo_get':
tone.c:196:9: error: 'ch_inf' undeclared (first use in this function)
tone.c:196:9: note: each undeclared identifier is reported only once for each function it appears in
tone.c: In function 'tone_out':
tone.c:219:11: error: dereferencing pointer to incomplete type
tone.c:220:11: error: dereferencing pointer to incomplete type
tone.c:222:45: error: dereferencing pointer to incomplete type
tone.c:222:56: error: dereferencing pointer to incomplete type
tone.c:229:32: error: dereferencing pointer to incomplete type
tone.c:229:50: error: dereferencing pointer to incomplete type
tone.c:232:32: error: dereferencing pointer to incomplete type
tone.c:232:50: error: dereferencing pointer to incomplete type
tone.c:240:2: warning: implicit declaration of function 'vcf_out' [-Wimplicit-function-declaration]
tone.c:240:53: error: dereferencing pointer to incomplete type
tone.c:240:68: error: dereferencing pointer to incomplete type
tone.c:241:53: error: dereferencing pointer to incomplete type
tone.c:241:68: error: dereferencing pointer to incomplete type
make: *** [tone.o] Error 1

toneが、まだ通りませんでした (>_<)

tone.c で ch と vcf への参照ありですね。

tone.c に ch.h と vcf.h の include行を追加

--> tone.c
#include "tone.h"
#include "ch.h"
#include "vcf.h"

struct note_rec note_buf[NOTE_BUF_N];

struct tone_rec tone_inf[] = {
  :
<-- tone.c
$ make tone.o
gcc -Wall    -c -o tone.o tone.c
tone.c: In function 'tone_out':
tone.c:221:11: error: dereferencing pointer to incomplete type
tone.c:222:11: error: dereferencing pointer to incomplete type
tone.c:224:45: error: dereferencing pointer to incomplete type
tone.c:224:56: error: dereferencing pointer to incomplete type
tone.c:231:32: error: dereferencing pointer to incomplete type
tone.c:231:50: error: dereferencing pointer to incomplete type
tone.c:234:32: error: dereferencing pointer to incomplete type
tone.c:234:50: error: dereferencing pointer to incomplete type
tone.c:242:53: error: dereferencing pointer to incomplete type
tone.c:242:68: error: dereferencing pointer to incomplete type
tone.c:243:53: error: dereferencing pointer to incomplete type
tone.c:243:68: error: dereferencing pointer to incomplete type
make: *** [tone.o] Error 1
$

tone.c 該当行をみてみると、stat_rec構造体のポインタ変数がらみです。

おそらく tone.c からのインクルードでは、 stat_rec構造体の宣言だけ追加した部分までしか含まれてなくて、 stat.h 本体のインクルードがされてないはずです

プリプロセッサ展開して、見てみましょう

$ gcc -E -o tone.i tone.c
$
$ grep 'struct stat_rec' tone.i
struct stat_rec;
double env_out(struct stat_rec *stat, double sec);
struct stat_rec;
void delay_buf_set(struct stat_rec *stat, double v, double env_v);
double delay_buf_lookup(struct stat_rec *stat, double sec, double *ret_env_v);
double delay_out(struct stat_rec *stat, struct delay_rec *delay, double *ret_env_v);
double chorus_out(struct stat_rec *stat);
double tone_out(struct stat_rec *stat, double freq, double env_v);
tone_out(struct stat_rec *stat, double freq, double env_v)
$
$ grep tone.h tone.i
# 1 "tone.h" 1
# 5 "tone.h" 2
# 6 "tone.h" 2
# 7 "tone.h" 2
# 8 "tone.h" 2
# 9 "tone.h" 2
# 10 "tone.h" 2
$ grep stat.h tone.i
$
$ rm tone.i
$

確かにそのようです。

tone.c に stat.h の inlcude行追加します

$ make tone.o
gcc -Wall    -c -o tone.o tone.c
$

ようやく通りました (^_^)

コンパイルの仕上げとリンク

最後に main.c のコンパイル

$ make main.o
main.c: In function 'header':
main.c:6:2: warning: implicit declaration of function 'rd_str_chk' [-Wimplicit-function-declaration]
main.c:6:2: warning: implicit declaration of function 'ERR' [-Wimplicit-function-declaration]
main.c:7:2: warning: implicit declaration of function 'rd_int' [-Wimplicit-function-declaration]
main.c: At top level:
main.c:15:17: warning: 'struct out_rec' declared inside parameter list [enabled by default]
main.c:15:17: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default]
main.c: In function 'data_out':
main.c:21:11: error: dereferencing pointer to incomplete type
main.c:21:21: error: dereferencing pointer to incomplete type
main.c:21:33: error: dereferencing pointer to incomplete type
  :

大量に出てます

$ make main.o | less

として確認してみると...

メッセージで見えてる範囲では、 rd, util, out, note, stat, ch あたりのincludeが不足のようです

該当のヘッダファイルを追加

$ make main.o
gcc -Wall    -c -o main.o main.c
$

いけましたね

では通しで確認しつつ、リンクしてみます

$ make clean
rm -f prog27 main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o *~
$ make
gcc -Wall    -c -o main.o main.c
gcc -Wall    -c -o vcf.o vcf.c
gcc -Wall    -c -o ch.o ch.c
gcc -Wall    -c -o delay.o delay.c
gcc -Wall    -c -o stat.o stat.c
gcc -Wall    -c -o note.o note.c
gcc -Wall    -c -o env.o env.c
gcc -Wall    -c -o tone.o tone.c
gcc -Wall    -c -o filter.o filter.c
gcc -Wall    -c -o lfo.o lfo.c
gcc -Wall    -c -o modu.o modu.c
gcc -Wall    -c -o vco.o vco.c
gcc -Wall    -c -o wave.o wave.c
gcc -Wall    -c -o out.o out.c
gcc -Wall    -c -o rd.o rd.c
gcc -Wall    -c -o util.o util.c
gcc -Wall -o prog27 main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o -lm
$

ほほう。通りました (^_^)

動作確認

元にしている prog25.c と、動作に違いがないか確かめてみます

$ gcc -o prog25 prog25.c -lm
$ time cat L3007_06.MID | ./prog25 > prog25.raw

real    9m27.550s
user    9m24.635s
sys     0m1.784s
$
$ time cat L3007_06.MID | ./prog27 > prog27.raw

real    9m32.262s
user    9m29.216s
sys     0m1.764s
$
$ cmp prog25.raw prog27.raw
$

OKです (^_^)

出力内容は一致してます

処理時間が増えたのは、最適化が効きにくくなったせいなのかもしれません

一旦、この状態のソース・コードを固めておきます

midi_prog.tgz

ファイル内スコープの変数と関数

処理速度を改善すべく(?)試してみます (^_^;

現状では、各モジュールの変数と関数は、 全てグローバルなものとして公開された状態です

自らのモジュールでしか参照しないものは、 static をつけてファイル内スコープに閉じてしまえば、 最適化が効いて inline 展開されるなどして、 少しは速度の改善が望めるかもしれません

自らのモジュールでしか参照しない変数と関数を見つけるために、 nm コマンドで *.o ファイルのシンボルを調べてみます

例えば ch.o では

$ make
  :
$ nm ch.o
00000180 C ch_inf
00000029 T lsb_set
00000000 T msb_set
$

2つ目のフィールドのアルファベットが種別で、 3つ目のフィールドがシンボル名です。

chモジュールでは 種別'C' (COMMON領域、未初期変数) として、変数ch_infを定義、 種別'T' (テキスト領域、関数)として、関数lsb_set, 関数msb_setを 定義してます

note.o では

$ nm note.o
00000027 r __func__.3807
         U ch_inf
         U chorus_out
         U delay_buf_set
         U delay_out
         U env_out
         U fprintf
00002800 C note_buf
0000001f T note_buf_free
0000003c T note_buf_init
00000000 T note_buf_is_free
00000065 T note_buf_search
00000419 T note_onoff
0000012e T note_out
000000f8 T note_to_freq
         U pow
         U stat_lst_alloc_init
         U stderr
         U tone_compo_get
         U tone_out
$

種別 'U' (未定義)として、chモジュールで定義されてる変数ch_infが表示されています。

よって noteモジュールから、chモジュールで定義してる変数ch_infを参照してる事がわかります

また、'U env_out' の行で、noteモジュールからどこかのモジュールの env_outを参照してる事がわかりますが、 定義してるモジュールは...当然 envモジュールですね

$ nm env.o
00000000 T env_out
         U log10
$

envモジュールでは関数evn_outが定義され、log10を参照してて、 この場合log10の定義は、数学ライブラリmath.a中になります

という具合に nmコマンドの実行結果から、 外部の参照は種別'U'、 定義は'T'が関数の定義、'C'が未初期化の変数の定義、 他'D'が初期化済の変数の定義と扱って、シンボルを調べます

まず定義シンボル一覧を得るために、 ' T '、' C '、' D ' を含む行を抽出してみます

$ make
  :
$ nm *.o | grep -e ' T ' -e ' C ' -e ' D '
00000180 C ch_inf
00000029 T lsb_set
00000000 T msb_set
0000021b T chorus_out
00000111 T delay_buf_lookup
00000000 T delay_buf_set
00000199 T delay_out
  :

ここからcutコマンドで、3つめのフィールドだけ取り出して、 ソートしておきます

$ nm *.o | grep -e ' T ' -e ' C ' -e ' D ' | cut -d ' ' -f 3 | sort > def.lst
$
$ wc -l def.lst
54 def.lst
$

54個の変数または関数があります

外部の参照として参照されてる変数または関数を抽出するのは、 ' U ' でgrep します

$ nm *.o | grep ' U ' | head
         U sin
         U log10
         U cos
         U sin
         U wave_out
         U bk
         U ch_inf
         U exit
         U fprintf
         U fwrite

1つ目のフィールドの表示が無いので、 行頭のスペースは削除してから、2つ目のフィールドを取り出して、 ソートしておきます

$ nm *.o | grep ' U ' | sed -e 's/^ *//' | cut -d ' ' -f 2 | sort > ref.lst
$
$ wc -l ref.lst
78 ref.lst
$

参照の方が多いのは、標準ライブラリなどに定義のあるシンボルの参照が 含まれているためです

あとは diff で差分をとって、定義して公開してるけど、 どこからも参照してないシンボルをあぶり出します

$ diff -u def.lst ref.lst | grep '^-[^-]' | sed 's/^-//'
bend_note_update
bk_buf
data_out
delay_buf_lookup
drum_tone_inf
header
main
note_buf_search
note_to_freq
rd_str
sox_bit_len_fmt
sox_version
stat_buf
stat_init
tbl_lookup
tone_inf
tones_lst

diff -u の結果を、grepコマンドで 行頭が'-'かつ2文字目が'-'以外で始まる行を抽出し、 最後に sedコマンドで行頭の'-'を削除してます。

grep の2文字目の指定は '--- def.lst ...' の行をはじきたいだけなので、

... | grep '^-' | sed -e '/^--/d' -e 's/^-//'

でもいいし、

... | sed -n 's/^-//p' | sed '/^-/d'

としても、何でもよろしゅうございます

では、結果のリストから、関数定義や変数定義にstaticをつけて、 ヘッダファイルの宣言を削除して試してみます。

例えばシンボル note_buf_search では

--> note.c
  :
static int // ここに static を追加
note_buf_search(int ch, int note, int onoff)
{
	int i;
  :
<-- note.c
--> note.h
  :
void note_buf_init(void);
//int note_buf_search(int ch, int note, int onoff); // この行を削除
double note_to_freq(int note);
  :
<-- note.h

main.c に定義があるものは .h は無いので static だけつけます。

ただし main関数だけはそのままで static をつけたりしません (^_^;

Makefile のプログラム名を prog27 から prog28 に変更しておきます

--> Makefile
CC = gcc -Wall
LIB = -lm
TARG = prog28
  :
<-- Makefile

ビルドしてエラー確認

$ make
gcc -Wall    -c -o main.o main.c
gcc -Wall    -c -o vcf.o vcf.c
gcc -Wall    -c -o ch.o ch.c
gcc -Wall    -c -o delay.o delay.c
gcc -Wall    -c -o stat.o stat.c
gcc -Wall    -c -o note.o note.c
gcc -Wall    -c -o env.o env.c
gcc -Wall    -c -o tone.o tone.c
gcc -Wall    -c -o filter.o filter.c
gcc -Wall    -c -o lfo.o lfo.c
gcc -Wall    -c -o modu.o modu.c
gcc -Wall    -c -o vco.o vco.c
gcc -Wall    -c -o wave.o wave.c
gcc -Wall    -c -o out.o out.c
gcc -Wall    -c -o rd.o rd.c
gcc -Wall    -c -o util.o util.c
gcc -Wall -o prog28 main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o -lm
$

問題なさそうですね

続いて動作確認

$ time cat L3007_06.MID | ./prog28 > prog28.raw

real    9m30.606s
user    9m28.188s
sys     0m0.916s
$
$ cmp prog27.raw prog28.raw
$

prog27 の結果と比べて、出力内容は一致してます。

処理速度は、ほんの少し向上 (^_^;

prog27 から prog28 の差分です

prog28.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/Makefile	2014-01-25 00:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 CC = gcc -Wall
 LIB = -lm
-TARG = prog27
+TARG = prog28
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o

 all: $(TARG)
diff -urN midi_prog-/delay.c midi_prog/delay.c
--- midi_prog-/delay.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/delay.c	2014-01-25 00:00:00.000000000 +0900
@@ -11,7 +11,7 @@
 	stat->delay_buf[1][i] = env_v;
 }

-double
+static double
 tbl_lookup(double *tbl, int n, double d_idx)
 {
 	int i;
@@ -22,7 +22,7 @@
 	return MIX(tbl[i], tbl[(i+1)%n], d_idx);
 }

-double
+static double
 delay_buf_lookup(struct stat_rec *stat, double sec, double *ret_env_v)
 {
 	double cycle;
diff -urN midi_prog-/delay.h midi_prog/delay.h
--- midi_prog-/delay.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/delay.h	2014-01-25 00:00:00.000000000 +0900
@@ -11,8 +11,6 @@
 };

 void delay_buf_set(struct stat_rec *stat, double v, double env_v);
-double tbl_lookup(double *tbl, int n, double d_idx);
-double delay_buf_lookup(struct stat_rec *stat, double sec, double *ret_env_v);
 double delay_out(struct stat_rec *stat, struct delay_rec *delay, double *ret_env_v);
 double chorus_out(struct stat_rec *stat);

diff -urN midi_prog-/main.c midi_prog/main.c
--- midi_prog-/main.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/main.c	2014-01-25 00:00:00.000000000 +0900
@@ -5,7 +5,7 @@
 #include "stat.h"
 #include "ch.h"

-int
+static int
 header(void)
 {
 	int v;
@@ -18,7 +18,7 @@
 	return v; /* div4 */
 }

-void
+static void
 data_out(struct out_rec *ot, double evt_sec)
 {
 	double vl, vr;
diff -urN midi_prog-/note.c midi_prog/note.c
--- midi_prog-/note.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/note.c	2014-01-25 00:00:00.000000000 +0900
@@ -24,7 +24,7 @@
 	for(i=0; i<NOTE_BUF_N; i++) note_buf_free(i);
 }

-int
+static int
 note_buf_search(int ch, int note, int onoff)
 {
 	int i;
@@ -38,7 +38,7 @@
 	return -1;
 }

-double
+static double
 note_to_freq(int note)
 {
 	return 440 * pow(2, (note - 69) / 12.0);
diff -urN midi_prog-/note.h midi_prog/note.h
--- midi_prog-/note.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/note.h	2014-01-25 00:00:00.000000000 +0900
@@ -23,8 +23,6 @@
 int note_buf_is_free(int i);
 void note_buf_free(int i);
 void note_buf_init(void);
-int note_buf_search(int ch, int note, int onoff);
-double note_to_freq(int note);
 int note_out(struct note_rec *nt, double *vl, double *vr);
 void note_onoff(int onoff, double evt_sec, int ch, int note, int velo);

diff -urN midi_prog-/out.c midi_prog/out.c
--- midi_prog-/out.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/out.c	2014-01-25 00:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 #include "out.h"

-int
+static int
 sox_version(void)
 {
 	FILE *fp;
@@ -12,7 +12,7 @@
 	return v1*10000 + v2*100 + v3;
 }

-char *
+static char *
 sox_bit_len_fmt(int bit_len)
 {
 	return sox_version() >= 140400 ?
diff -urN midi_prog-/out.h midi_prog/out.h
--- midi_prog-/out.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/out.h	2014-01-25 00:00:00.000000000 +0900
@@ -17,8 +17,6 @@
 	double smp_t, sec;
 };

-int sox_version(void);
-char *sox_bit_len_fmt(int bit_len);
 void out_init(struct out_rec *ot, int ac, char **av);
 void out_do(struct out_rec *ot, double v);

Binary files midi_prog-/prog27 and midi_prog/prog27 differ
diff -urN midi_prog-/rd.c midi_prog/rd.c
--- midi_prog-/rd.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/rd.c	2014-01-25 00:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 #include "rd.h"

-int bk_buf = -1;
+static int bk_buf = -1;

 int
 rd(void)
@@ -19,7 +19,7 @@
 	bk_buf = v;
 }

-void
+static void
 rd_str(int n, char *s)
 {
 	int i;
diff -urN midi_prog-/rd.h midi_prog/rd.h
--- midi_prog-/rd.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/rd.h	2014-01-25 00:00:00.000000000 +0900
@@ -3,11 +3,8 @@

 #include "util.h"

-extern int bk_buf;
-
 int rd(void);
 void bk(int v);
-void rd_str(int n, char *s);
 int rd_str_chk(int n, char *s);
 int rd_int(int n);
 int rd_delta(void);
diff -urN midi_prog-/stat.c midi_prog/stat.c
--- midi_prog-/stat.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/stat.c	2014-01-25 00:00:00.000000000 +0900
@@ -1,8 +1,8 @@
 #include "stat.h"

-struct stat_rec stat_buf[ STAT_BUF_N ];
+static struct stat_rec stat_buf[ STAT_BUF_N ];

-void
+static void
 stat_init(struct stat_rec *stat, double sec)
 {
 	int i;
diff -urN midi_prog-/stat.h midi_prog/stat.h
--- midi_prog-/stat.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/stat.h	2014-01-25 00:00:00.000000000 +0900
@@ -20,9 +20,6 @@
 	struct stat_rec *next;
 };

-extern struct stat_rec stat_buf[ STAT_BUF_N ];
-
-void stat_init(struct stat_rec *stat, double sec);
 void stat_buf_init(void);
 void stat_lst_free(struct stat_rec *stat);
 struct stat_rec *stat_lst_alloc_init(int n, double sec, struct note_rec *nt, struct tone_compo_rec *tone_compo_arr, struct stat_rec *next);
diff -urN midi_prog-/tone.c midi_prog/tone.c
--- midi_prog-/tone.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/tone.c	2014-01-25 00:00:00.000000000 +0900
@@ -3,7 +3,7 @@
 #include "vcf.h"
 #include "stat.h"

-struct tone_rec tone_inf[] = {
+static struct tone_rec tone_inf[] = {
 	{	/* strings */
 		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
 		{ LPF, 3000, 1.5 }, { OFF, },
@@ -99,7 +99,7 @@

 #define PROG_DRUM	0

-struct{
+static struct{
 	int prog, note; /* note for ch 9 */
 	struct tone_compo_rec *tone_compo;
 } tones_lst[] = {
diff -urN midi_prog-/tone.h midi_prog/tone.h
--- midi_prog-/tone.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/tone.h	2014-01-25 00:00:00.000000000 +0900
@@ -26,7 +26,6 @@
 	double rate;
 };

-extern struct tone_rec tone_inf[], drum_tone_inf[];
 struct tone_compo_rec *tone_compo_get(int ch, int note, int *ret_n);
 double tone_out(struct stat_rec *stat, double freq, double env_v);

prog27 の midi_prog.tgz と prog28.patch からは、 次の手順でビルドします

$ tar xzf midi_prog.tgz
$ cd midi_prog
$ patch -p1 < ../prog28.patch
$ make


エイリアスノイズ

全然外してるかも知れないのですが、 エイリアスノイズとやらの対策にトライしてみます

そもそもエイリアスノイズとは、そしてその対策とは?

http://www.groovecube.com/ja/tutorial/tech_alias.html

素晴らしい説明のページ がありました m(__)m

ナイキスト周波数(サンプリング周波数の半分)より上のノイズが、 ナイキスト周波数で折り返されて、変なノイズが聞こえて困ると...

そしてその対策も、 オシレータの波形からあらかじめナイキスト周波数以上の成分を除去しておいて、 対応すればいいと

なーるほどです。

となると、オシレータの処理は wave.c の wave_out() 関数です

double
wave_out(int wave, double cycle)
{
	cycle -= (int)cycle;

	switch(wave){
	case WAVE_SIN:
		return sin(2 * M_PI * cycle);
	case WAVE_SAW:
		cycle *= 2;
		if(cycle > 1) cycle -= 2;
		return cycle;
	case WAVE_SQUARE:
		return cycle < 0.5 ? 1 : -1;
	case WAVE_NOISE:
		return ((double)rand() / RAND_MAX) * 2 - 1;
	}
	return 0;
}

ノコギリ波 (WAVE_SAW)、矩形波 (WAVE_SUQRE) の生成処理が、モロに相当してますですね

さて予め除去...って、サンプリング周波数はコマンドのパラメータとして指定してて可変です。

初期化処理でノコギリ波1周期分のデータを用意して、指定のサンプリング周波数でカット・オフ決めて、 LPF処理で波形を鈍らせて...

って、発音周波数ごとに1周期分の処理結果の波形は異なるのでは?

そのデータを発音周波数ごとに用意するとなると...

なんか、起動の処理が重くなるし、波形生成処理も遅くなりそうです。

うーむ。うまい方法はないものか?

要するにフィルタで鈍らせたのと同等になればいいのかな?

だとしたら、こんなアイデアはどうでしょう?

サイクルとして指定する位置を、点ではなく、領域で考えます。

前回指定したサイクル位置から、今回指定したサイクル位置までの値を、 積分して、サイクルの間隔で割算して平均すればいいのかな?

wave_out()関数の呼び出し元をたぐると、vco.c vco_out()関数

void
vco_stat_update(struct vco_stat_rec *stat, double freq, double sec)
{
	stat->cycle += freq * (sec - stat->sec);
	stat->freq = freq;
	stat->sec = sec;
}

double
vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
{
	if(modu_v != 0) freq *= pow(2, modu_v);
	vco_stat_update(stat, freq, sec);
	return wave_out(wave, stat->cycle);
}

vco_stat_update()関数では、指定時刻と、状態として記録されてる前回の処理時刻との差を求め、 記録されている波形のサイクル、時刻を更新。

wave_out()関数を呼び出して、「点として」サイクル位置を指定して、該当のサイクルの値を取得してます。

例えば、statのupdate処理の前に cycleをバックアップしておいて、 wave_out()で前回と、今回の波形の値を求めて、足して2で割る。

って、それでは積分になってませんね。 ノコギリでも、矩形でも、不連続のところをまたぐと、ダメダメです

ならば wave_out()の処理中で、予め積算した値を扱ってやればどうでしょうか?

矩形なら、X方向をサイクル、Y方向を-1から+1で考えると

x=0の位置からの積分を考えると

x=0.5までは単調増加で、x=0.5のときは面積からして0.5

x=0.5を超えると単調減少で、x=1までいくと0

cycle の値 0〜1 までとして

cycle < 0.5 ? cycle : 1 - cycle

になります

ノコギリも同様に考えると

x=0の位置からの積分を考えると

x=0.5までは2次関数で増加して、x=0.5のときは面積からして0.25

x=0.5を超えると2次関数で減少で、x=1までいくと0

(むー。図に無理がある...)

cycle の値 0〜1 までとして

cycle < 0.5 ? cycle * cycle : (1 - cycle) * (1 - cycle)

になります

この積分結果を使った波形出力関数を追加してみます。

wave_out2(int wave, double cycle_prev, double cycle)

として、1つ前のサイクルも cycle_prev として指定します。

waveで指定する種類が、ノコギリか矩形ならば、 cycle_prev から cycle までを積分した結果を、 cycle - cycle_prev で割算して、この間の平均値を返します

waveの指定がノコギリでも矩形でもなければ、 従来通り wave_out() を呼び出して、cycle_prev の指定は無視します。

cycle_prev と cycle が同じときも、従来通り wave_out() を使います

vco.c の vco_out()からは、 1つ前のサイクルと、現在のサイクルを指定して、 wave_out2()を呼び出すように変更します

wave.c の実装としては、波形の種類に論理和で指定するフラグとして、

#define WAVE_INTEGRAL	(1<<8)

を追加しておいて、wave_out2() から内部的に wave_out() を呼び出すときに、 指定するようにしてみました

prog28 から prog29 の差分です

prog29.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	2014-01-25 00:00:00.000000000 +0900
+++ midi_prog/Makefile	2014-01-26 00:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 CC = gcc -Wall
 LIB = -lm
-TARG = prog28
+TARG = prog29
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o

 all: $(TARG)
diff -urN midi_prog-/vco.c midi_prog/vco.c
--- midi_prog-/vco.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/vco.c	2014-01-26 00:00:00.000000000 +0900
@@ -11,9 +11,12 @@
 double
 vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)
 {
+	double cycle_prev;
+
 	if(modu_v != 0) freq *= pow(2, modu_v);
+	cycle_prev = stat->cycle;
 	vco_stat_update(stat, freq, sec);
-	return wave_out(wave, stat->cycle);
+	return wave_out2(wave, cycle_prev, stat->cycle);
 }

 /* EOF */
diff -urN midi_prog-/wave.c midi_prog/wave.c
--- midi_prog-/wave.c	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/wave.c	2014-01-26 00:00:00.000000000 +0900
@@ -16,8 +16,35 @@
 		return cycle < 0.5 ? 1 : -1;
 	case WAVE_NOISE:
 		return ((double)rand() / RAND_MAX) * 2 - 1;
+
+	case WAVE_INTEGRAL | WAVE_SAW:
+		return cycle < 0.5 ? cycle * cycle : (1 - cycle) * (1 - cycle);
+	case WAVE_INTEGRAL | WAVE_SQUARE:
+		return cycle < 0.5 ? cycle : 1 - cycle;
 	}
 	return 0;
 }

+double
+wave_out2(int wave, double cycle_prev, double cycle)
+{
+	double d, v, v_prev;
+
+	switch(wave){
+	case WAVE_SIN:
+	case WAVE_NOISE:
+		return wave_out(wave, cycle);
+	}
+	cycle -= (int)cycle;
+	cycle_prev -= (int)cycle_prev;
+	d = cycle - cycle_prev;
+	if(d < 0) d += 1;
+	if(d == 0) return wave_out(wave, cycle);
+
+	wave |= WAVE_INTEGRAL;
+	v = wave_out(wave, cycle);
+	v_prev = wave_out(wave, cycle_prev);
+	return (v - v_prev) / d;
+}
+
 /* EOF */
diff -urN midi_prog-/wave.h midi_prog/wave.h
--- midi_prog-/wave.h	2014-01-24 00:00:00.000000000 +0900
+++ midi_prog/wave.h	2014-01-26 00:00:00.000000000 +0900
@@ -7,7 +7,9 @@
 #define WAVE_SAW	1
 #define WAVE_SQUARE	2
 #define WAVE_NOISE	3
+#define WAVE_INTEGRAL	(1<<8)

 double wave_out(int wave, double cycle);
+double wave_out2(int wave, double cycle_prev, double cycle);

 #endif

この程度の演算の追加なら、さほど負荷も変わらないように思います。

そして、果たして、違いが判るほどの効果があるものなのか...?

prog27 の midi_prog.tgz からは、次の手順でビルドします

$ tar xzf midi_prog.tgz
$ cat prog28.patch prog29.patch | (cd midi_prog ; patch -p1 ; make)

とりあえず、実行を試してみます

$ time cat L3007_06.MID | ./prog29 > prog29.raw

real    10m44.106s
user    10m42.880s
sys     0m0.256s
$
$ cmp prog28.raw prog29.raw
prog28.raw prog29.raw differ: char 694041, line 1
$

処理時間は増えてます。

そしてデータ的にも確かに変化しています。

$ cat prog28.raw | play -t raw -r 44100 -b 16 -s -c 2 -
  :
$ cat prog29.raw | play -t raw -r 44100 -b 16 -s -c 2 -
  :

うーむ。違いがよく判らんです orz

ですが、サンプリング周波数を16kHzまで落して比べてみると...

$ cat L3007_06.MID | ./prog28 -r 16000 -play
  :
$ cat L3007_06.MID | ./prog29 -r 16000 -play
  :

判る! 違いが判ります (^_^v

改めて 44100Hz で聴き比べてみると

$ cat L3007_06.MID | ./prog28 -play
  :
$ cat L3007_06.MID | ./prog29 -play
  :

prog29_xaa.mp3

prog29_xab.mp3

ハイハットの音が全然変わってます。

やかんを叩くような音だったのが、牛乳瓶を叩くような音になってます。

適当に作ってみたハイハットの音色は、 実はエイリアスノイズのおかげで金属っぽく聴こえていたようです。


お試し演奏ツール

まずはアイデアをお試し

CUI14 を作ってたので、随分間が開いてしまいました。

ここまでのプログラムはSMFファイルを喰わせる前提としてきました。

音色のパラメータを少し変えて感触を試すときも、起動してSMFのデータを与えねばなりません。

このプログラムはそのままで、鍵盤をちょっと叩いて音を出すような感覚で、試したりできないものか?

プログラムで対応してるSMFの構造は先述の通りで、ヘッダがあって、次にトラックがあります。

トラックでは ID、サイズときて、あとはデルタタイム、イベントの組がひたすら続きます。 プログラムでは、イベントがきたら、その時刻までの波形を生成してから、そのイベント処理してます。

ならば、このプログラムの前段に、SMFのデータを生成しつづけるようなプログラムを繋いでみたら、なんとかなりそうです。

この前段のプログラムが、ユーザが鍵盤をちょっと押したとか、離したとかの入力を受け付けて、 それに対応したイベントや時刻のデータを出力すればどうでしょうか?

前段のプログラムで、動的にSMFを生成しつづけるようなイメージです。

ただし、鍵盤を押した場合、デルタタイムとノートONのイベントを出力しても、その音の波形はまだ生成されないはずです。

前のイベントから、そのノートONのイベントまでの間の波形データが出力される事になります。

ならば、前段のプログラムからは、処理されないようなダミーのイベントを生成して、定期的にデルタタイムを進めてやれば、 ダミーイベントの時刻までの波形は生成できるでしょう。

イベントの表をみてみると、1バイト目が 0xf8 のイベントは、 サイズ1バイトのイベントで、MIDIクロック・イベントと呼ばれ、四分音符あたり24回送信されるイベントのようです

プログラムの main.c を見てみると、1バイト目に 0xf8 が来た場合では、対応してないのですが...ダメです。

1バイト目上位4ビットが 0xf なら、すぐ2バイト目をリードしてします。

1バイト目上位4ビットが 0xf でも、下位4ビットが 8, 0xa, 0xb, 0xc, 0xe のときは、2バイト目は無いので不要です。

ここだけはどうしても、修正する必要があります。

prog30 として修正しておきます

prog29 から prog30 の差分です

prog30.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	2014-01-26 00:00:00.000000000 +0900
+++ midi_prog/Makefile	2014-04-04 00:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 CC = gcc -Wall
 LIB = -lm
-TARG = prog29
+TARG = prog30
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o

 all: $(TARG)
diff -urN midi_prog-/main.c midi_prog/main.c
--- midi_prog-/main.c	2014-01-25 00:00:00.000000000 +0900
+++ midi_prog/main.c	2014-04-04 00:00:00.000000000 +0900
@@ -164,7 +164,18 @@
 			rd();
 			break;
 		case 0xf:
-			type = rd();
+			switch(low){
+			case 8:
+			case 0xa:
+			case 0xb:
+			case 0xc:
+			case 0xe:
+				break;
+			default:
+				type = rd();
+				break;
+			}
+
 			switch(low){
 			case 0:
 				while(rd() != 0xf7);

前段のプログラムで、このクロック・イベントを定期的に出力すればよさそうです。

イベントの処理は何もしないで、その時刻までの波形を生成させたいだけなので、四分音符あたり24回とかは、別に気にしなくてもいいでしょう。

ただ間を開けすぎるとレスポンスが鈍くなって、困るかもしれません

四分音符あたり24回って、どんなもんでしょうか?

ターゲットのSMFでは96分割で、テンポはだいたい四分音符120個/分でした。

四分音符1つが500msec。

24分割すると、およそ20msec程度。

50Hzくらいでクロックのイベントを生成する感じです

次に気になるのが、トラックの冒頭のサイズのデータ。

イベントを生成し続けるので、この先どこまで続くかわからないので、トラックのサイズは未定です。

でも問題ありませんでした

main.c
  :
int
main(int ac, char **av)
{
  :
	div4 = header();

	if(!rd_str_chk(4, "MTrk")) ERR("track id");
	v = rd_int(4); /* skip */

	hi = low = 0;
	delta_sum = 0;
	while((v = rd_delta()) != EOF){
		delta_sum += v;
  :

ヘッダを処理した後、トラックのIDを確認した後、 トラックのサイズはスキップしてました。

以降は、デルタタイムとイベントのループへ。

ここは、このままで使えます

では、簡単なプログラムで、どんな感じか試してみます

test_play.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

void
wr_str(char *s)
{
	printf("%s", s);
	fflush(stdout);
}

void
wr_int(int n, int v) /* big endian */
{
	while(n--) putchar((v >> n*8) & 0xff);
	fflush(stdout);
}

void
wr_delta(int v)
{
	char buf[5], *bp;

	if(v == 0){
		putchar(0);
		fflush(stdout);
		return;
	}
	bp = buf;
	while(v){
		*bp++ = v & 0x7f;
		v >>= 7;
	}
	while(--bp != buf) putchar(0x80 | *bp);
	putchar(*bp);
	fflush(stdout);
}

int
now_msec(void)
{
	struct timeval tm;
	gettimeofday(&tm, NULL);
	return tm.tv_sec * 1000 + tm.tv_usec / 1000;
}

void
wr_delta_now(int st_msec, int *sum, int div)
{
	int msec, t;
	msec = now_msec() - st_msec;
	t = div * msec / 500;
	wr_delta(t - *sum);
	*sum = t;
}

void
wait_msec(int msec, int st_msec, int *sum, int div)
{
	int st;
	st = now_msec();
	while(now_msec() - st < msec){
		wr_delta_now(st_msec, sum, div);
		wr_int(1, 0xf8); /* clock */
		usleep(20*1000);
	}
}

int
main(int ac, char **av)
{
	int div, st_msec, sum;

	div = 96;
	wr_str("MThd");
	wr_int(4, 6);
	wr_int(2, 0);
	wr_int(2, 1);
	wr_int(2, div);

	wr_str("MTrk");
	wr_int(4, 0);

	sum = -30;
	st_msec = now_msec();

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (0xc<<4) | 0); /* prog_num ch=0 */
	wr_int(1, 50); /* 50 : strings ensamble 2 */

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (0xb<<4) | 0); /* ctl chg ch=0 */
	wr_int(1, 7); /* channel volme msb */
	wr_int(1, 64);

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (9<<4) | 0); /* note on ch=0 */
	wr_int(1, 69); /* A */
	wr_int(1, 64); /* velo */

	wait_msec(500, st_msec, &sum, div);

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (9<<4) | 0); /* note on ch=0 */
	wr_int(1, 73); /* C# */
	wr_int(1, 64); /* velo */

	wait_msec(500, st_msec, &sum, div);

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (9<<4) | 0); /* note on ch=0 */
	wr_int(1, 76); /* E */
	wr_int(1, 64); /* velo */

	wait_msec(3000, st_msec, &sum, div);

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (8<<4) | 0); /* noe off ch=0 */
	wr_int(1, 69); /* A */
	wr_int(1, 0); /* velo */

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (8<<4) | 0); /* noe off ch=0 */
	wr_int(1, 73); /* C# */
	wr_int(1, 0); /* velo */

	wr_delta_now(st_msec, &sum, div);
	wr_int(1, (8<<4) | 0); /* noe off ch=0 */
	wr_int(1, 76); /* E */
	wr_int(1, 0); /* velo */

	wait_msec(1000, st_msec, &sum, div);

	return 0;
}

/* EOF */

まず本体をビルド

$ tar xzf midi_prog.tgz
$ cat prog28.patch prog29.patch prog30.patch | (cd midi_prog ; patch -p1 ; make)
$ ln -s midi_prog/prog30

として、シンボリックリンクを貼っておきます

懐かしのデータ確認用のプログラム prog_onoff8 のビルド

$ gcc -o prog_onoff8 prog_onoff8.c -lm

そして、お試しツールのビルド

$ gcc -o test_play test_play.c

まず出力内容の確認

$ ./test_play | hd
00000000  4d 54 68 64 00 00 00 06  00 00 00 01 00 60 4d 54  |MThd.........`MT|
00000010  72 6b 00 00 00 00 00 c0  32 00 b0 07 40 00 90 45  |rk......2...@..E|
00000020  40 00 f8 03 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |@...............|
00000030  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 03  |................|
00000040  f8 04 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 03  |................|
00000050  f8 04 f8 04 90 49 40 00  f8 04 f8 04 f8 04 f8 04  |.....I@.........|
00000060  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
00000070  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 03  |................|
00000080  f8 04 f8 04 f8 04 f8 04  f8 04 90 4c 40 00 f8 04  |...........L@...|
00000090  f8 04 f8 03 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
000000a0  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
000000b0  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 03  |................|
000000c0  f8 04 f8 04 f8 04 f8 04  f8 04 f8 04 f8 03 f8 04  |................|
*
000000e0  f8 04 f8 04 f8 04 f8 04  f8 04 f8 03 f8 04 f8 04  |................|
*
00000100  f8 04 f8 04 f8 04 f8 04  f8 03 f8 04 f8 04 f8 04  |................|
*
00000120  f8 04 f8 04 f8 04 f8 03  f8 04 f8 04 f8 04 f8 04  |................|
*
00000140  f8 04 f8 04 f8 03 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
*
00000160  f8 04 f8 03 f8 04 f8 04  f8 04 f8 04 f8 04 f8 03  |................|
00000170  f8 04 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
00000180  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 03 f8 04  |................|
00000190  f8 04 f8 04 f8 04 f8 04  f8 04 f8 04 f8 03 f8 04  |................|
000001a0  f8 04 f8 04 f8 04 f8 04  f8 04 f8 03 f8 04 f8 04  |................|
000001b0  f8 04 f8 04 f8 04 f8 04  f8 03 80 45 00 00 80 49  |...........E...I|
000001c0  00 00 80 4c 00 00 f8 04  f8 04 f8 04 f8 04 f8 04  |...L............|
000001d0  f8 04 f8 04 f8 03 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
000001e0  f8 04 f8 03 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
*
00000200  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 04  |................|
00000210  f8 03 f8 04 f8 04 f8 04  f8 04 f8 04 f8 04 f8 03  |................|
00000220  f8 04 f8 04 f8 04 f8 04  f8                       |.........|
00000229
$

続いて懐かしのツールでデータが読めるか確認

$ ./test_play | ./prog_onoff8
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=0
sec=0.000 prog num ch=0 v=50
sec=0.000 ctl chg ch=0 type=7 v=64
sec=0.000 on ch=0 note=69 velo=64
sec=0.000 c1=0xf8, c2=0x03
$

ダメでした

クロックイベント 0xf8 で2バイト目を解釈しようとしてて、 さらに見知らぬイベントだったため、そこで終了してます

せっかくなので、prog_onoff9.c として 0xf8 の対応だけ追加しておきます

prog_onoff9.c
#include <stdio.h>
#include <math.h>

int
main()
{
	int i, v, c, c1, c2, low, hi;
	char id[5];
	int div4, delta_sum;
	double sec;
	int tempo, tempo_delta_sum;
	double tempo_sec;

	printf("header\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("format type=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("track num=%d\n", v);

	v = 0;
	for(i=0; i<2; i++) v = (v << 8) | getchar();
	printf("time division=%d (0x%x)\n\n", v, v);
	div4 = v;


	printf("track\n");

	for(i=0; i<4; i++) id[i] = getchar();
	id[i] = '\0';
	printf("id='%s'\n", id);

	v = 0;
	for(i=0; i<4; i++) v = (v << 8) | getchar();
	printf("size=%d\n", v);

	tempo = 500000;
	tempo_delta_sum = 0;
	tempo_sec = 0;

	delta_sum = 0;
	while(1){
		v = 0;
		do{
			if((c = getchar()) == EOF) break;
			v = (v << 7) | (c & 0x7f);
		}while(c & 0x80);
		if(c == EOF) break;
		delta_sum += v;
		sec = tempo_sec + (double)(delta_sum - tempo_delta_sum) / div4 * tempo * 0.000001;
		printf("sec=%.3f ", sec);

		c = getchar();
		if(c & 0x80){
			c1 = c;
			hi = (c1 >> 4) & 0xf;
			low = c1 & 0xf;
			if(c == 0xf8){
				printf("midi clock\n");
				continue;
			}
			c2 = getchar();
		}else{
			c2 = c;
		}

		if(hi == 9 || hi == 8){
			printf("%s ch=%d ", hi == 9 ? "on" : "off", low);
			printf("note=%d ", c2);
			printf("velo=%d\n", getchar());
			continue;
		}

		if(c1 == 0xff){
			printf("meta event type=%d ", c2);
			v = getchar();
			printf("len=%d ", v);

			if(c2 == 0x51){ /* set tempo */
				tempo = 0;
			}
			for(i=0; i<v; i++){
				c = getchar(); /* skip */
				if(i < 16) printf("0x%02x ", c);
				if(c2 == 0x51){ /* set tempo */
					tempo <<= 8;
					tempo |= c;
				}
			}
			printf("\n");
			if(c2 == 0x51){ /* set tempo */
				tempo_delta_sum = delta_sum;
				tempo_sec = sec;
			}
			continue;
		}

		if(c1 == 0xf0){
			printf("sys ex len=%d ...\n", c2);
			while(getchar() != 0xf7);
			continue;
		}

		if(hi == 0xb){
			printf("%s ch=%d type=%d v=%d\n",
			       c2 < 120 ? "ctl chg" : "chg mode msg",
			       low, c2, getchar());
			continue;
		}

		if(hi == 0xc){
			printf("prog num ch=%d v=%d\n", low, c2);
			continue;
		}

		if(hi == 0xe){
			printf("pitch wheel change ch=%d lsb=%d msb=%d\n", low, c2, getchar());
			continue;
		}

		printf("c1=0x%02x, c2=0x%02x\n", c1, c2);
		break;
	}
	return 0;
}

/* EOF */

改めて

$ gcc -o prog_onoff9 prog_onoff9.c -lm
$ ./test_play | ./prog_onoff9
  :
sec=4.927 midi clock
sec=4.948 midi clock
sec=4.969 midi clock
sec=4.990 midi clock
sec=5.005 midi clock
$

5秒間くらいの間のイベントが、スクロールで流れていきました

$ ./test_play | ./prog_onoff9 | less

でみてみると

header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=0
sec=0.000 prog num ch=0 v=50
sec=0.000 ctl chg ch=0 type=7 v=64
sec=0.000 on ch=0 note=69 velo=64
sec=0.000 midi clock
sec=0.016 midi clock
sec=0.036 midi clock
sec=0.057 midi clock
sec=0.078 midi clock
sec=0.099 midi clock
sec=0.120 midi clock
sec=0.141 midi clock
sec=0.156 midi clock
sec=0.177 midi clock
sec=0.198 midi clock
sec=0.219 midi clock
sec=0.240 midi clock
sec=0.260 midi clock
sec=0.281 midi clock
sec=0.297 midi clock
sec=0.318 midi clock
sec=0.339 midi clock
sec=0.359 midi clock
sec=0.380 midi clock
sec=0.401 midi clock
sec=0.422 midi clock
sec=0.443 midi clock
sec=0.458 midi clock
sec=0.479 midi clock
sec=0.500 on ch=0 note=73 velo=64
sec=0.500 midi clock
sec=0.521 midi clock
  :

問題なさそうです

では、鳴らしてみます

$ ./test_play | ./prog30 -play
  :
In:0.00% 00:00:00.93 [00:00:00.00] Out:35.6k [      |      ]        Clip:0    play WARN alsa: under-run
In:0.00% 00:00:01.67 [00:00:00.00] Out:71.3k [      |      ]        Clip:0    play WARN alsa: under-run

ほほー。鳴りました (^_^v

でも、under-run がポロポロでてます

最初のイベントに対し、2番目移行のイベントの時刻が少し後方になるように細工すれば、 データが溜ってアンダーランが改善されるかも?

最初の時刻取得を、過去の方向に少しズラして試してみます

--- test_play.c-	Fri Apr  4 00:00:00 2014
+++ test_play.c	Fri Apr  4 01:00:00 2014
@@ -83,7 +83,7 @@
 	wr_str("MTrk");
 	wr_int(4, 0);

-	sum = 0;
+	sum = -30;
 	st_msec = now_msec();

 	wr_delta_now(st_msec, &sum, div);

試してる環境では、これでアンダーランが出なくなりました。

-30 を -25 にすると、アンダーランが出てしまいます

4分音符1つ500msecを96分割した単位のなので

$ echo '500/96*30' | bc -l
156.24999999999999999990
$

157 msec、なかなかの大きい時間のズレかもしれません

デルタタイム追加処理を分離

なんとかなりそうな感触があったという事で、この線で進めていってみます

前段の test_play で、SMFのデータをその場で生成して、 後段の prog30 に渡して試しました

ここで、前段の test_play の処理をさらに分割しておきます

test_play の出力側の処理をまとめて、独立させてみます

イベント・データを入力として受け取り、 その時の時刻をみてデルタタイムを生成して付加し、 デルタタイムとイベントの組として出力する処理としてまとめます。

まず冒頭のヘッダのデータを、入力として受け取って、 これはそのまま出力側にスルーします。

以降は、ループでイベントのデータが来るのを待ちながら、 20 msec 間隔で、デルタタイムと、 MIDIクロック・イベント (0xf8 , 1バイト)との組を、出力し続けます。

入力からイベントのデータがきたら、デルタタイムを生成して、 デルタタイムとイベントのデータの組を出力します

これらの処理を、プログラム add_delta としてまとめておけば、 上流の処理も単純になりそうです

例えばコマンドとして

$ ./add_delta | ./prog30 -play

と起動して、標準入力へヘッダのデータを渡し、 その後、自由なタイミングで、標準入力へとイベントデータを渡せば、 prog30 から起動されてる play コマンドへの波形データに反映されるはずです

となると、また入力からのイベント・データの末尾判定問題が浮上します

イベント・データのサイズを知るには、データの中身を解釈して、 判断するしかありませんでした。

イベントとは

その処理は既に prog30 の中でやってますが、 前段の add_delta では、入力から受け取ったバイナリデータは、 出力へとスルーするだけで、中身の解釈までは不要です。

ここではイベントの末尾だけ判ればよいのですが、 さてどうすべきでしょうか?

「サイズ」「データ列」の組で渡すのも一つの手ですが、 「サイズ」についてすぐさま

などの仕様を決めて、さらに前段の出力をそれに合わねばなりません。

なるべく単純にしたいので、ここは一つ、 エスケープ文字を使い、デリミタを判定する仕様を考えてみます

これだけの決まりなら、例えば次のような感じで処理も単純になりそうです

出力側

void
wrt(char *data, int n)
{
	int i;
	for(i=0; i<n; i++){
		if(data[i] == '\\') putchar ('\\');
		putchar(data[i]);
	}
	putchar('\\');
	putchar('\0');
}

入力側

int
rd(char *data, int n)
{
	int i = 0, c;
	while(i <; n && (c = getchar()) != EOF){
		if(c == '\\') if(getchar() != '\\') break;
		data[i++] = c;
	}
	return i;
}

それでは add_delta.c として実装

add_delta.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

int
now_msec(void)
{
	struct timeval tm;
	gettimeofday(&tm, NULL);
	return tm.tv_sec * 1000 + tm.tv_usec / 1000;
}

void
wr_delta(int v)
{
	char buf[8], *bp;

	if(v < 0 || v > 100){
		fprintf(stderr, "v=%d", v);
		exit(1);
	}

	if(v == 0){
		putchar(0);
		fflush(stdout);
		return;
	}
	bp = buf;
	while(v){
		*bp++ = v & 0x7f;
		v >>= 7;
	}
	while(--bp != buf) putchar(0x80 | *bp);
	putchar(*bp);
	fflush(stdout);
}

int
readable_chk(void)
{
	int fd = 0;
	fd_set rfs;
	struct timeval tm;

	FD_ZERO(&rfs);
	FD_SET(fd, &rfs);
	tm.tv_sec = 0;
	tm.tv_usec = 0;
	return select(fd+1, &rfs, NULL, NULL, &tm) > 0;
}

int
my_getchar(void)
{
	char c;
	int n = read(0, &c, 1);
	if(n <= 0) return EOF;
	return c;
}

int
rd(char *data, int n)
{
	int i = 0, c;
	while(i < n && (c = my_getchar()) != EOF){
		if(c == '\\') if(my_getchar() != '\\') break;
		data[i++] = c;
	}
	if(i == 0 && c == EOF) return -1;
	return i;
}

#define TEMPO	120
#define MSEC4

int st_msec, last_msec, sum, div_n = 96;

#define BUFN	4096
char buf[ BUFN ];

void
wr_delta_now(void)
{
	int t, msec;
	last_msec = now_msec();
	msec = last_msec - st_msec;
#if 1
	t = (int)(div_n * msec * 0.001 * (TEMPO / 60.0));
#else
	t = div_n * msec * TEMPO / (60*1000);
#endif
	wr_delta(t - sum);
	sum = t;
}

void
wr_clock_now(void)
{
	char ev;
	wr_delta_now();
	ev = 0xf8;
	fwrite(&ev, 1, 1, stdout);
	fflush(stdout);
}


int
main(int ac, char **av)
{
	int n, i;

	while((n = rd(buf, BUFN)) <= 0);
	fwrite(buf, 1, n, stdout);
	fflush(stdout);

	i = 4+4+2+2;
	if(n >= i+2) div_n = (buf[i] << 8) | buf[i+1];

	sum = -30;
	st_msec = now_msec();
	wr_clock_now();
	while(1){
		if(readable_chk()){
			if((n = rd(buf, BUFN)) < 0) break;
			wr_delta_now();
			fwrite(buf, 1, n, stdout);
			fflush(stdout);
		}
		if(now_msec() - last_msec >= 60*1000 / (TEMPO * 24)) wr_clock_now();
		usleep(1000);
	}
	return 0;
}

/* EOF */

上流側の処理も test_play と同等になるように test_play2.c として作ってみます

test_play2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

void
wrt(char *data, int n)
{
	int i;
	for(i=0; i<n; i++){
		if(data[i] == '\\') putchar ('\\');
		putchar(data[i]);
	}
	putchar('\\');
	putchar('\0');
	fflush(stdout);
}

#define BUFN	4096
char buf[ BUFN ];
int bufn = 0;

void
wrt_buf(void)
{
	if(bufn <= 0) return;
	wrt(buf, bufn);
	bufn = 0;
}

void
set_char(char c)
{
	if(bufn >= BUFN) exit(1); /* give up */
	buf[ bufn++ ] = c;
}

void
set_str(char *s)
{
	int i;
	for(i=0; s[i]; i++) set_char(s[i]);
}

void
set_int(int n, int v) /* big endian */
{
	while(n--) set_char((v >> n*8) & 0xff);
}

int
main(int ac, char **av)
{
	set_str("MThd");
	set_int(4, 6);
	set_int(2, 0);
	set_int(2, 1);
	set_int(2, 96);

	set_str("MTrk");
	set_int(4, 0);

	wrt_buf();

	set_char((0xc<<4) | 0); /* prog_num ch=0 */
	set_char(50); /* 50 : strings ensamble 2 */
	wrt_buf();

	set_char((0xb<<4) | 0); /* ctl chg ch=0 */
	set_char(7); /* channel volme msb */
	set_char(64);
	wrt_buf();

	set_char((9<<4) | 0); /* note on ch=0 */
	set_char(69); /* A */
	set_char(64); /* velo */
	wrt_buf();

	usleep(500*1000);

	set_char((9<<4) | 0); /* note on ch=0 */
	set_char(73); /* C# */
	set_char(64); /* velo */
	wrt_buf();

	usleep(500*1000);

	set_char((9<<4) | 0); /* note on ch=0 */
	set_char(76); /* E */
	set_char(64); /* velo */
	wrt_buf();

	usleep(3000*1000);

	set_char((8<<4) | 0); /* noe off ch=0 */
	set_char(69); /* A */
	set_char(0); /* velo */
	wrt_buf();

	set_char((8<<4) | 0); /* noe off ch=0 */
	set_char(73); /* C# */
	set_char(0); /* velo */
	wrt_buf();

	set_char((8<<4) | 0); /* noe off ch=0 */
	set_char(76); /* E */
	set_char(0); /* velo */
	wrt_buf();

	usleep(1000*1000);

	return 0;
}

/* EOF */

$ gcc -o add_delta add_delta.c
$ gcc -o test_play2 test_play2.c

出力の内容を確認します

$ ./test_play2 | ./add_delta | ./prog_onoff9 > res_onoff

test_play の時の結果も

$ ./test_play | ./prog_onoff9 > res_onoff.old

新旧の結果を比べてみます

$ paste -d '\t' res_onoff.old res_onoff | sed 's/\t/\t|\t/' | less

header  |       header
id='MThd'       |       id='MThd'
size=6  |       size=6
format type=0   |       format type=0
track num=1     |       track num=1
time division=96 (0x60) |       time division=96 (0x60)
        |
track   |       track
id='MTrk'       |       id='MTrk'
size=0  |       size=0
sec=0.156 prog num ch=0 v=50    |       sec=0.156 midi clock
sec=0.156 ctl chg ch=0 type=7 v=64      |       sec=0.156 prog num ch=0 v=50
sec=0.156 on ch=0 note=69 velo=64       |       sec=0.156 ctl chg ch=0 type=7 v=64
sec=0.156 midi clock    |       sec=0.156 on ch=0 note=69 velo=64
sec=0.172 midi clock    |       sec=0.177 midi clock
sec=0.193 midi clock    |       sec=0.198 midi clock
sec=0.214 midi clock    |       sec=0.214 midi clock
sec=0.234 midi clock    |       sec=0.234 midi clock
sec=0.255 midi clock    |       sec=0.255 midi clock
sec=0.276 midi clock    |       sec=0.276 midi clock
sec=0.297 midi clock    |       sec=0.297 midi clock
sec=0.312 midi clock    |       sec=0.318 midi clock
sec=0.333 midi clock    |       sec=0.339 midi clock
sec=0.354 midi clock    |       sec=0.354 midi clock
sec=0.375 midi clock    |       sec=0.375 midi clock
sec=0.396 midi clock    |       sec=0.396 midi clock
sec=0.417 midi clock    |       sec=0.417 midi clock
sec=0.432 midi clock    |       sec=0.438 midi clock
sec=0.453 midi clock    |       sec=0.458 midi clock
sec=0.474 midi clock    |       sec=0.479 midi clock
sec=0.495 midi clock    |       sec=0.495 midi clock
sec=0.516 midi clock    |       sec=0.516 midi clock
sec=0.536 midi clock    |       sec=0.536 midi clock
sec=0.557 midi clock    |       sec=0.557 midi clock
sec=0.578 midi clock    |       sec=0.578 midi clock
sec=0.594 midi clock    |       sec=0.599 midi clock
sec=0.615 midi clock    |       sec=0.615 midi clock
sec=0.635 midi clock    |       sec=0.635 midi clock
sec=0.656 on ch=0 note=73 velo=64       |       sec=0.651 on ch=0 note=73 velo=64
sec=0.656 midi clock    |       sec=0.672 midi clock
sec=0.677 midi clock    |       sec=0.693 midi clock
sec=0.698 midi clock    |       sec=0.714 midi clock
  :

若干表示が見にくいですが

  :
sec=4.115 midi clock    |       sec=4.146 midi clock
sec=4.135 midi clock    |       sec=4.156 off ch=0 note=69 velo=0
sec=4.156 midi clock    |       sec=4.156 off ch=0 note=73 velo=0
sec=4.177 off ch=0 note=69 velo=0       |       sec=4.156 off ch=0 note=76 velo=0
sec=4.177 off ch=0 note=73 velo=0       |       sec=4.177 midi clock
sec=4.177 off ch=0 note=76 velo=0       |       sec=4.198 midi clock
sec=4.177 midi clock    |       sec=4.219 midi clock
sec=4.198 midi clock    |       sec=4.240 midi clock
sec=4.219 midi clock    |       sec=4.260 midi clock
sec=4.240 midi clock    |       sec=4.281 midi clock
  :

  :
sec=5.083 midi clock    |       sec=5.125 midi clock
sec=5.104 midi clock    |       sec=5.141 midi clock
sec=5.125 midi clock    |
sec=5.141 midi clock    |
sec=5.161 midi clock    |
(END)

だいたい同じようなデータが出力できてます

では音を確認

$ ./test_play2 | ./add_delta | ./prog30 -play

同じように鳴ってます

OKです (^_^v

CUIから音出ししてみる

test_play2 として試してる処理を、UI を使って置き換えれるか試してみます

CUI14

ソースコード cui100.tgz

パッチファイル cui100_101.patch

を引っ張ってきます

このCUIの環境を使って、test_play2 の様に、イベント・データを出力するプログラムを作ってみます。

と言ってもまず最初は、もの凄く汎用なショボイUIだけを用意して試してみます

CUIの環境では、標準出力は画面描画に使っているので、 test_play2 コマンドのように、標準出力を後続のコマンドにパイプ '|' で繋ぐ事はできません。

なのでCUIのプログラムの中から popen() で コマンド "./add_delta | ./prog30 -play" を "w" モードで実行します。

そして、そのファイルポインタに、イベントデータを書き込んで試してみます

ここで問題が発生しました。

CUIの環境から、popen() による ./prog30 -play の実行で、CUIの画面が乱れてしまいました soxのplayコマンドが、警告メッセージや進捗メッセージを、ガンガン表示する事が原因でした prog30 からの play コマンドの実行で、オプション -V0 -q を与えれば、黙らす事ができます この処理を追加するため、prog31 としてプログラムを更新しておきます

prog30 から prog31 の差分です

prog31.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	Fri Apr  4 00:00:00 2014
+++ midi_prog/Makefile	Sat Apr  5 22:00:00 2014
@@ -1,6 +1,6 @@
 CC = gcc -Wall
 LIB = -lm
-TARG = prog30
+TARG = prog31
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o

 all: $(TARG)
diff -urN midi_prog-/out.c midi_prog/out.c
--- midi_prog-/out.c	Sat Jan 25 00:00:00 2014
+++ midi_prog/out.c	Sat Apr  5 22:00:00 2014
@@ -41,6 +41,8 @@
 		strcat(cmd, "sox");
 		fn = opt_str("-sox", ac, av, "-d");
 	}
+	if(opt_idx("-V0", ac, av) >= 0) strcat(cmd, " -V0");
+	if(opt_idx("-q", ac, av) >= 0) strcat(cmd, " -q");
 	if(cmd[0]){
 		sprintf(cmd + strlen(cmd), " -t raw -r %d %s %s -c %d - %s",
 			ot->smp_freq, sox_bit_len_fmt(ot->bit_len),

prog31 の オプションとして -V0 や -q が指定されていると、 -play, -sox オプションで playコマンドや、soxコマンドを起動する際に、 指定されている -V0 や -q オプションを追加して起動するように修正しました

$ tar xzf midi_prog.tgz
$ cat prog28.patch prog29.patch prog30.patch prog31.patch | (cd midi_prog ; patch -p1 ; make)
$ ln -s midi_prog/prog31
$ ./test_play2 | ./add_delta | ./prog31 -play -V0 -q

これで表示を黙らせて音が鳴ります

そうした上で、CUIのプログラムを cui_midi.c として実装して試してみます

cui_midi.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include "cui/cui.h"
#include "cui/scpanel.h"
#include "cui/button.h"
#include "cui/handler.h"
#include "cui/menu.h"
#include "cui/num.h"
#include "cui/tab.h"
#include "cui/kbd.h"
#include "cui/timer.h"
#include "cui/arg.h"

FILE *fp = NULL;
#define BUFN	4096
char buf[ BUFN ], *bp = buf;

void
set_char(char c)
{
	if(bp - buf >=BUFN) ERR("BUFN give up");
	*bp++ = c;
}

void
set_str(char *s)
{
	while(*s) set_char(*s++);
}

void
set_int(int n, int v)
{
	while(n--) set_char((v >> n*8) & 0xff);
}

void
wrt(void)
{
	int i, n = bp - buf;
	if(n <= 0) return;
	for(i=0; i<n; i++){
		if(buf[i] == '\\') fputc('\\', fp);
		fputc(buf[i], fp);
	}
	fputc('\\', fp);
	fputc('\0', fp);
	fflush(fp);
	bp = buf;
}

void
set_char2_wrt(char c1, char c2)
{
	set_char(c1);
	set_char(c2);
	wrt();
}

void
set_char3_wrt(char c1, char c2, char c3)
{
	set_char(c1);
	set_char(c2);
	set_char(c3);
	wrt();
}

struct test4_ch{
	int prog, vol, velo;
	cui kbd;
	int pmode, msec;
	int arpe_note;
};

struct test4{
	cui mn_ch, prog, vol, velo;
	int midi_ch, bak_ch;
	struct test4_ch ch[16];
	cui pmode, on, msec, play, repeat;
};

static void
note_onoff(int note, int onoff, void *prm)
{
	struct test4 *p = (struct test4 *)prm;
	struct test4_ch *ch = &p->ch[ p->midi_ch ];
	int hi = onoff ? 9 : 8;
	int low = p->midi_ch;
	set_char3_wrt((hi<<4) | low, note, ch->velo);
}

static void
arpe_note_off(void *prm)
{
	struct test4 *p = (struct test4 *)prm;
	struct test4_ch *ch = &p->ch[ p->midi_ch ];
	if(ch->arpe_note < 0) return;
	note_onoff(ch->arpe_note, FALSE, prm);
	ch->arpe_note = -1;
}

static int
repeat_hdr(cui obj, int evt, int val, void *prm)
{
	struct test4 *p = (struct test4 *)prm;
	struct test4_ch *ch = &p->ch[ p->midi_ch ];
	int i, note, prev = ch->arpe_note;

	arpe_note_off(prm);
	for(note=0; note<128; note++) if(cui_kbd_get(ch->kbd, note)) break;
	if(note < 128){
		for(i=0; i<128; i++){
			note = (prev + 1 + i) % 128;
			if(cui_kbd_get(ch->kbd, note)) break;
		}
		note_onoff(note, TRUE, prm);
		ch->arpe_note = note;
	}
	return TRUE;
}

int
t4_btn_hdr(cui obj, int evt, int val, void *prm)
{
	/* CUI_EVT_BUTTON */

	struct test4 *p = (struct test4 *)prm;
	struct test4_ch *ch = &p->ch[ p->midi_ch ];

	if(obj == p->mn_ch){
		cui_num_vp_set(p->prog, &ch->prog);
		cui_num_vp_set(p->vol, &ch->vol);
		cui_num_vp_set(p->velo, &ch->velo);
		cui_hide(p->ch[ p->bak_ch ].kbd);
		cui_show(ch->kbd);
		cui_menu_popup_vp_set(p->pmode, &ch->pmode);
		cui_num_vp_set(p->msec, &ch->msec);
		p->bak_ch = p->midi_ch;
	}else if(obj == p->prog){
		set_char2_wrt((0xc<<4) | p->midi_ch, ch->prog);
	}else if(obj == p->vol){
		set_char3_wrt((0xb<<4) | p->midi_ch, 7 /* vol msb */, ch->vol);
	}else if(obj == ch->kbd){
		char *s = cui_menu_popup_str_get(p->pmode);
		if(strcmp(s, "Immediate") == 0){
			int onoff = val & 0x100;
			int note = val & ~0x100;
			note_onoff(note, onoff, prm);
		}
	}else if(obj == p->pmode){
		char *s = cui_menu_popup_str_get(p->pmode);
		cui_show_hide(p->on, strcmp(s, "Chord") == 0);
		cui_show_hide(p->msec, strcmp(s, "Arpeggio") == 0);
		cui_show_hide(p->play, strcmp(s, "Arpeggio") == 0);
		cui_show_hide(p->repeat, strcmp(s, "Arpeggio") == 0);
	}else if(obj == p->on){
		int note;
		for(note=0; note<128; note++){
			if(!cui_kbd_get(ch->kbd, note)) continue;
			note_onoff(note, val, prm);
		}
	}else if(obj == p->play){
		int note, prev;
		prev = -1;
		for(note=0; note<128; note++){
			if(!cui_kbd_get(ch->kbd, note)) continue;
			if(prev >= 0) note_onoff(prev, FALSE, prm);
			note_onoff(note, TRUE, prm);
			usleep(ch->msec * 1000);
			prev = note;
		}
		note_onoff(prev, FALSE, prm);
	}else if(obj == p->repeat){
		cui_timer_set(obj, val ? ch->msec : -1, FALSE);
		arpe_note_off(prm);
	}else return FALSE;
	return TRUE;
}

int
main(int ac, char **av)
{
	int init_ret = cui_init(av);
	int w = cui_arg_int(av, "-w", 45, NULL);
	int h = cui_arg_int(av, "-h", 11, NULL);
	cui root = cui_scpanel_new(NULL, 0, 0, w, h, "cui_midi");
	cui bs4 = cui_scpanel_sheet_get(root);
	struct test4 t4;
	int div = cui_arg_int(av, "-div", 96, NULL);
	int i;

	cui_hide(bs4);
	t4.midi_ch = t4.bak_ch = 0;
	for(i=0; i<16; i++){
		t4.ch[i].prog = 0;
		t4.ch[i].vol = 0;
		t4.ch[i].velo = 64;
		t4.ch[i].pmode = 0;
		t4.ch[i].msec = 200;
		t4.ch[i].arpe_note = -1;
	}
	t4.mn_ch = cui_menu_int_new(bs4, 0, 0, 2+1, 0, 0, 15);
	cui_wlb_name_new(bs4, "ch ", NULL, NULL);
	cui_menu_int_vp_set(t4.mn_ch, &t4.midi_ch);

	t4.prog = cui_num_new(bs4, CUI_CHILD_X, CUI_CHILD_Y2+1, 3+3, 0, 0, 127, 0);
	cui_wlb_name_new(bs4, "prog ", NULL, NULL);

	t4.vol = cui_num_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, 3+3, 0, 0, 127, 0);
	cui_wlb_name_new(bs4, "vol (msb) ", NULL, NULL);

	t4.velo = cui_num_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, 3+3, 0, 0, 127, 0);
	cui_wlb_name_new(bs4, "velo ", NULL, NULL);

	for(i=0; i<16; i++){
		t4.ch[i].kbd = cui_kbd_new(bs4, 0, cui_y2(t4.velo->parent)+1, 40);
		cui_hide(t4.ch[i].kbd);
		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	}

	t4.pmode = cui_menu_popup_new(bs4, 0, CUI_CHILD_Y2+1, 9+1,
				      (char *[]){ "Immediate", "Chord", "Arpeggio", NULL}, 0);
	t4.on = cui_ckbox_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, "ON", FALSE);
	cui_hide(t4.on);

	t4.msec = cui_num_new(bs4, CUI_CHILD_X, CUI_CHILD_Y, 4+3, 200, 0, 1000, 0);
	cui_hide(t4.msec);

	t4.play = cui_button_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, "play");
	cui_hide(t4.play);

	t4.repeat = cui_ckbox_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, "repeat", FALSE);
	cui_hide(t4.repeat);

	cui_wh_fit(bs4);
	cui_show(bs4);

	cui_bind(t4.mn_ch, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.prog, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.vol, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.pmode, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.on, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.play, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.repeat, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
	cui_bind(t4.repeat, CUI_EVT_TIMER, repeat_hdr, &t4);

	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));

	fp = stdout;

	set_str("MThd");
	set_int(4, 6);
	set_int(2, 0);
	set_int(2, 1);
	set_int(2, div);
	set_str("MTrk");
	set_int(4, 0);
	wrt();

	cui_main(root, NULL);

	cui_fini(init_ret);
	return 0;
}

/* EOF */

まず CUI のライブラリをビルドします

$ tar xzf cui100.tgz
$ cat cui100_101.patch | (cd cui100 ; patch -p1)
$ mv cui100 cui
$ (cd cui ; make lib)

これで cui/libcui.a が 生成されます

次に cui_midi をビルドします

$ gcc -o cui_midi cui_midi.c -Lcui -lcui

そして実行

$ ./cui_midi

この状態で、add_delta からは、クロック・イベントが出てるので、 prog31 では、すでに無音の波形をせっせと生成して、 playコマンドに与え続けています

test_play2.c の冒頭で出力してイベントの一部を、出力して試してみます。

一発目のCUIなので、単純すぎて、操作の手間が恐ろしくかかります...;-p)

MIDIチャンネル 0 のプログラム番号を 50 (strings ensamble 2) に設定してみます。

0xc0 , 0x32(50) の 2バイトのイベントです

上矢印キー、左矢印キー、下矢印キーで、左側の'0'のポップアップメニューにフォーカスします
ENTERキーでメニューを開いて、上下矢印キーで移動して、'C' を選択し ENTERキー

右矢印キー、右矢印キーで、(Add)ボタンにフォーカスし ENTER

下のラベルに 'c0' の表示が追加されます

続いて、左、左、左 で左側の'C'のポップアップメニューにフォーカスしてENTER
'3'を選択します

右で右側の'0'のポップアップメニューにフォーカスしてENTER
'2'を選択します

(Add)ボタンで 32 の表示が追加されます

(Out)ボタンで 0xc0 0x32 のイベントが出力されて、溜ってた c0 32 の表示が消えるというわけです
# あぁ操作がしんどい...

続いてコントロール・チェンジの channel volume MSB で、 MDIチャンネル0 のボリュームを 0x40 (64) に設定します。

0xb0 , 0x07 , 0x40(64) の 3バイトのイベントです

矢印キーで、ポップアップメニューを操作して、'B' '0' にして (Add)ボタン
矢印キーで、ポップアップメニューを操作して、'0' '7' にして (Add)ボタン
矢印キーで、ポップアップメニューを操作して、'4' '0' にして (Add)ボタン
(Out)ボタンでイベント出力完了です

続いてMIDIチャンネル0で、ノート番号69 (Aの音) でノートONイベント。

ベロシティは 0x40(64) を指定してみます。

0x90 , 0x45(69) , 0x40(64) の3バイトのイベントです

矢印キーで、ポップアップメニューを操作して、'9' '0' にして (Add)ボタン
矢印キーで、ポップアップメニューを操作して、'4' '5' にして (Add)ボタン
矢印キーで、ポップアップメニューを操作して、'4' '0' にして (Add)ボタン
(Out)ボタンでイベント出力完了で、音が出るはずです

音を止めるには、ノートOFFイベント。

ベロシティは 0x00 を指定してみます。

0x80 , 0x45(69) , 0x00 の3バイトのイベントです

矢印キーで、ポップアップメニューを操作して、'8' '0' にして (Add)ボタン
矢印キーで、ポップアップメニューを操作して、'4' '5' にして (Add)ボタン
矢印キーで、ポップアップメニューを操作して、'0' '0' にして (Add)ボタン
(Out)ボタンでイベント出力完了で、音が止まります
# あぁ本当に操作がしんどい...

終了は (Quit) フォーカスで ENTER です

CUIから音出ししてみる (その2)

少しは操作の手間を向上できないものかと、 CUI14 の方で 数値入力 用の部品 cui_num を追加してみました。

これを使って、少しは使えそうな画面を考えてみます

cui/ ディレクトリを更新しておきます。

パッチファイル

cui101_102.patch
diff -urN cui101/Makefile cui102/Makefile
--- cui101/Makefile	Sun Apr  6 22:00:00 2014
+++ cui102/Makefile	Sun Apr  6 23:00:00 2014
@@ -7,7 +7,7 @@
 endif

 TARG = cui_test
-OBJS = cui.o focus.o etext.o menu.o dialog.o timer.o term.o scpanel.o tbar.o rszbox.o scbar.o fillbtn.o tab.o radio.o ckbox.o button.o lblfix.o fill.o label.o panel.o handler.o esc.o key.o rect.o dbg.o list.o
+OBJS = cui.o focus.o num.o etext.o menu.o dialog.o timer.o term.o scpanel.o tbar.o rszbox.o scbar.o fillbtn.o tab.o radio.o ckbox.o button.o lblfix.o fill.o label.o panel.o handler.o esc.o key.o rect.o dbg.o list.o

 all: $(TARG)

diff -urN cui101/cui_test.c cui102/cui_test.c
--- cui101/cui_test.c	Tue Apr  1 23:10:00 2014
+++ cui102/cui_test.c	Sun Apr  6 23:00:00 2014
@@ -18,6 +18,7 @@
 #include "scpanel.h"
 #include "term.h"
 #include "timer.h"
+#include "num.h"

 int
 joke_hdr(cui obj, int evt, int val, void *prm)
@@ -327,6 +328,18 @@
 }

 int
+num_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+	cui lb_num = (cui)prm;
+	static char buf[ CUI_ETEXT_BSIZE ];
+
+	sprintf(buf, "%d", val);
+	cui_label_str_set(lb_num, buf);
+	return TRUE;
+}
+
+int
 main(int ac, char **av)
 {
 	int dbg_ret = cui_dbg_open(ac, av);
@@ -344,6 +357,11 @@
 	cui lb_timer1 = cui_label_new(bs_timer, 0, 1, "0   ");
 	cui lb_timer2 = cui_label_new(bs_timer, 10, 1, "not yet");
 	cui lb_timer3 = cui_label_new(bs_timer, 10, 2, "not yet");
+	cui bs_num = cui_base_new(bs_view, 0, 1, 0, 0);
+	cui num = cui_num_new(bs_num, 1, 1, 20, 0, -5, 5, 0);
+	cui num2 = cui_num_new(bs_num, 1, 3, 20, 0, -128, 127, 0);
+	cui num3 = cui_num_new(bs_num, 1, 5, 20, 0, 0, 0, 0);
+	cui lb_num = cui_label_new(bs_num, 1, 7, "");
 	cui base = cui_base_new(bs_view, 0, 1, 40, 20);
 	cui mn_btn = cui_menu_btn_new(base, 1, 1, "File");
 	cui etx = cui_etext_new(base, mn_btn->x, cui_y2(mn_btn), 8, "foo");
@@ -355,7 +373,7 @@
 	cui small = cui_scpanel_new(base, 2, 3, base->w - (2 * 2), base->h - (3 + 1), "title");
 	cui small2 = cui_scpanel_sheet_get(small);

-	cui tab_sheets[] = { base, bs_bar, sc_hoge, bs_term, bs_timer };
+	cui tab_sheets[] = { base, bs_bar, sc_hoge, bs_term, bs_timer, bs_num };

 	char lb_timer1_buf[16];

@@ -394,7 +412,13 @@
 	cui_timer_set_bind(lb_timer2, 200, FALSE, lb_timer_hdr, NULL);
 	cui_timer_set_bind(lb_timer3, 10*1000, TRUE, lb_timer_hdr, NULL);

-	cui_tab_new(bs_view, 0, 0, -1, (char *[]){"foo", "bar", "hoge", "term", "timer", NULL}, tab_sheets, 0);
+	cui_hide(bs_num);
+	cui_bind(num, CUI_EVT_BUTTON, num_hdr, lb_num);
+	cui_bind(num2, CUI_EVT_BUTTON, num_hdr, lb_num);
+	cui_bind(num3, CUI_EVT_BUTTON, num_hdr, lb_num);
+	cui_wh_fit(bs_num);
+
+	cui_tab_new(bs_view, 0, 0, -1, (char *[]){"foo", "bar", "hoge", "term", "timer", "num", NULL}, tab_sheets, 0);

 	cui_main(bs, NULL);
 	printf("result %s\n", p->btn_result);
diff -urN cui101/etext.h cui102/etext.h
--- cui101/etext.h	Sun Feb  2 22:20:00 2014
+++ cui102/etext.h	Sun Apr  6 23:00:00 2014
@@ -26,6 +26,7 @@

 cui cui_etext_new(cui parent, int x, int y, int w, char *init_s);
 void cui_etext_init(cui obj, cui parent, int x, int y, int w, char *init_s);
+void cui_etext_hist_del(cui obj, char *str);
 void cui_etext_hist_add(cui obj);
 void cui_etext_cursor_update(cui obj);
 int cui_etext_hdr(cui obj, int evt, int val, void *prm);
diff -urN cui101/num.c cui102/num.c
--- cui101/num.c	Thu Jan  1 09:00:00 1970
+++ cui102/num.c	Sun Apr  6 23:00:00 2014
@@ -0,0 +1,131 @@
+#include "num.h"
+#include "etext.h"
+#include "fillbtn.h"
+#include "scbar.h"
+#include "handler.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+cui
+cui_num_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v, int bar_h)
+{
+	cui obj = cui_alloc(sizeof(struct cui_num));
+	cui_num_init(obj, parent, x, y, w, init_v, min_v, max_v, bar_h);
+	return obj;
+}
+
+void
+cui_num_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v, int bar_h)
+{
+	cui_num p = (cui_num)obj;
+	char buf[ CUI_ETEXT_BSIZE ];
+
+	w = w < 6 ? 6 : w;
+	cui_base_init(obj, parent, x, y, w, 1);
+
+	if(min_v == max_v){ /* default */
+		min_v = INT_MIN / 32;
+		max_v = INT_MAX / 32;
+	}
+	init_v = init_v < min_v ? min_v : init_v;
+	init_v = init_v > max_v ? max_v : init_v;
+	p->v = init_v;
+	p->min_v = min_v;
+	p->max_v = max_v;
+	sprintf(buf, "%d", p->v);
+
+	if(bar_h == 0) bar_h = w;
+
+	p->fb_dec = cui_fillbtn_new(obj, 0, 0, 1, 1, "<");
+	p->etx = cui_etext_new(obj, 1, 0, w-3, buf);
+	p->fb_inc = cui_fillbtn_new(obj, cui_x2(p->etx), 0, 1, 1, ">");
+	p->fb_bar = cui_fillbtn_new(obj, cui_x2(p->fb_inc), 0, 1, 1, "|");
+
+	p->bs_bar = cui_base_new(cui_root(obj), 0, 0, 0, 0);
+	cui_hide(p->bs_bar);
+	p->sc_max = max_v - min_v;
+	p->bar = cui_scbar_new(p->bs_bar, 1, 1, 1, bar_h, p->sc_max, 0);
+	p->fb_x = cui_fillbtn_new(p->bs_bar, 1, 0, 1, 1, "x");
+	cui_wh_fit(p->bs_bar);
+	p->bs_bar->w++;
+
+	cui_bind(p->etx, CUI_EVT_BUTTON, cui_num_hdr, obj);
+	cui_bind(p->fb_dec, CUI_EVT_BUTTON, cui_num_hdr, obj);
+	cui_bind(p->fb_inc, CUI_EVT_BUTTON, cui_num_hdr, obj);
+	cui_bind(p->fb_bar, CUI_EVT_BUTTON, cui_num_hdr, obj);
+
+	cui_bind(p->bar, CUI_EVT_BUTTON, cui_num_hdr, obj);
+	cui_bind(p->fb_x, CUI_EVT_BUTTON, cui_num_hdr, obj);
+}
+
+static void
+update(cui obj)
+{
+	cui_num p = (cui_num)obj;
+	char buf[ CUI_ETEXT_BSIZE ];
+	char *s = cui_etext_str_get(p->etx);
+
+	if(strncmp(s, "0x", 2) == 0) sprintf(buf, "0x%x", (unsigned int)p->v);
+	else sprintf(buf, "%d", p->v);
+	cui_etext_str_set(p->etx, buf);
+}
+
+int
+cui_num_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	cui num = (cui)prm;
+	cui_num p = (cui_num) num;
+
+	if(obj == p->etx){
+		char *s = cui_etext_str_get(p->etx);
+		cui_etext_hist_del(p->etx, s);
+		p->v = strtol(s, NULL, 0);
+		if(p->v > p->max_v) p->v = p->max_v;
+		if(p->v < p->min_v) p->v = p->min_v;
+		update(num);
+		cui_etext_hist_add(p->etx);
+		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
+		return TRUE;
+	}else if(obj == p->fb_inc){
+		if(p->v >= p->max_v) return TRUE;
+		p->v++;
+		update(num);
+		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
+		return TRUE;
+	}else if(obj == p->fb_dec){
+		if(p->v <= p->min_v) return TRUE;
+		p->v--;
+		update(num);
+		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
+		return TRUE;
+	}else if(obj == p->fb_bar){
+		cui root = cui_root(obj);
+		int x = cui_lx(root, cui_gx(obj)) - 1;
+		int y = cui_ly(root, cui_gy(obj)) - p->bs_bar->h / 2;
+		cui focus = ((cui_scbar)p->bar)->thumb;
+
+		cui_scbar_val_set(p->bar, p->sc_max - p->v + p->min_v);
+		y = y < 0 ? 0 : y;
+		cui_xy_set(p->bs_bar, x, y);
+		p->bs_bar->flags &= ~CUI_FLG_HIDE;
+		cui_main(p->bs_bar, focus);
+		cui_hide(p->bs_bar);
+		cui_draw(p->bs_bar->parent);
+		return TRUE;
+	}else if(obj == p->bar){
+		p->v = p->sc_max - val + p->min_v;
+		update(num);
+		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
+		return TRUE;
+	}else if(obj == p->fb_x){
+		cui_quit();
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* EOF */
diff -urN cui101/num.h cui102/num.h
--- cui101/num.h	Thu Jan  1 09:00:00 1970
+++ cui102/num.h	Sun Apr  6 23:00:00 2014
@@ -0,0 +1,18 @@
+#ifndef __NUM_H__
+#define __NUM_H__
+
+#include "cui.h"
+
+typedef struct cui_num{
+	struct cui_base base;
+	cui fb_dec, etx, fb_inc, fb_bar;
+	cui bs_bar, bar, fb_x;
+	int v, min_v, max_v;
+	int sc_max;
+} *cui_num;
+
+cui cui_num_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v, int bar_h);
+void cui_num_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v, int bar_h);
+int cui_num_hdr(cui obj, int evt, int val, void *prm);
+
+#endif

を引っ張ってきます

$ (cd cui ; make clean)
$ cat cui101_102.patch | (cd cui ; patch -p1)
$ (cd cui ; make lib)

cui_midi.c の差分です

cui_midi2.patch
--- cui_midi.c-	Sat Apr  5 22:00:00 2014
+++ cui_midi.c	Mon Apr  7 00:00:00 2014
@@ -7,6 +7,7 @@
 #include "cui/button.h"
 #include "cui/handler.h"
 #include "cui/menu.h"
+#include "cui/num.h"

 typedef struct hex_in{
 	struct cui_base base;
@@ -75,6 +76,23 @@
 	bp = buf;
 }

+void
+set_char2_wrt(char c1, char c2)
+{
+	set_char(c1);
+	set_char(c2);
+	wrt();
+}
+
+void
+set_char3_wrt(char c1, char c2, char c3)
+{
+	set_char(c1);
+	set_char(c2);
+	set_char(c3);
+	wrt();
+}
+
 int
 btn_hdr(cui obj, int evt, int val, void *prm)
 {
@@ -106,6 +124,62 @@
 	return FALSE;
 }

+struct test2{
+	cui lb_ch, mn_ch;
+	cui   lb_prog, prog, prog_out;
+	cui   lb_vol, vol, vol_out;
+	cui   lb_onoff, mn_note, lb_note_num, note_num, lb_velo, velo, note_out;
+	cui   bs_note_len;
+	cui     lb_note_len, note_len, lb_ms;
+};
+
+static int
+num_get(cui obj)
+{
+	cui_num p = (cui_num)obj;
+	return p->v;
+}
+
+int
+t2_btn_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	struct test2 *p = (struct test2 *)prm;
+	int ch = ((cui_menu_popup)p->mn_ch)->val;
+
+	if(obj == p->prog_out){
+		set_char2_wrt((0xc<<4) | ch, num_get(p->prog));
+		return TRUE;
+	}else if(obj == p->vol_out){
+		set_char3_wrt((0xb<<4) | ch, 7 /* vol msb */, num_get(p->vol));
+		return TRUE;
+	}else if(obj == p->note_out){
+		char *s = cui_menu_popup_str_get(p->mn_note);
+		int note_num = num_get(p->note_num);
+		int velo = num_get(p->velo);
+		if(strcmp(s, "on") == 0){
+			set_char3_wrt((9<<4) | ch, note_num, velo);
+			return TRUE;
+		}else if(strcmp(s, "off") == 0){
+			set_char3_wrt((8<<4) | ch, note_num, velo);
+			return TRUE;
+		}else if(strcmp(s, "on/off") == 0){
+			int msec = num_get(p->note_len);
+			set_char3_wrt((9<<4) | ch, note_num, velo);
+			usleep(msec * 1000);
+			set_char3_wrt((8<<4) | ch, note_num, velo);
+			return TRUE;
+		}
+	}else if(obj == p->mn_note){
+		char *s = cui_menu_popup_str_get(p->mn_note);
+		if(strcmp(s, "on/off") == 0) cui_show(p->bs_note_len);
+		else cui_hide(p->bs_note_len);
+		return TRUE;
+	}
+	return FALSE;
+}
+
 int
 main(int ac, char **av)
 {
@@ -122,10 +196,35 @@
 	cui lb = cui_label_new(scp->view, 2, cui_y2(hxi)+1, "");
 	cui prm_arr[] = { hxi, lb };

+	cui bs2 = cui_base_new(scp->view, 2, cui_y2(lb)+1, 0, 0);
+	char *ch_lst[] = {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15",NULL};
+	char *note_lst[] = {"on", "off", "on/off", NULL};
+	struct test2 t2;
+
 	char *cmd = "./add_delta | ./prog31 -V0 -q -play";
 	int div = 96;
 	int i;

+	t2.lb_ch = cui_label_new(bs2, 0, 0, "midi ch : ");
+	t2.mn_ch = cui_menu_popup_new(bs2, cui_x2(t2.lb_ch), 0, 4, ch_lst, 0);
+	t2.lb_prog = cui_label_new(bs2, 2, 2, "prog num : ");
+	t2.prog = cui_num_new(bs2, cui_x2(t2.lb_prog), 2, 8, 50, 0, 127, 0);
+	t2.prog_out = cui_button_new(bs2, cui_x2(t2.prog)+2, 2, "Out");
+	t2.lb_vol = cui_label_new(bs2, 2, 4, "vol (msb) : ");
+	t2.vol = cui_num_new(bs2, cui_x2(t2.lb_vol), 4, 8, 64, 0, 127, 0);
+	t2.vol_out = cui_button_new(bs2, cui_x2(t2.vol)+2, 4, "Out");
+	t2.lb_onoff = cui_label_new(bs2, 2, 6, "note : ");
+	t2.mn_note = cui_menu_popup_new(bs2, cui_x2(t2.lb_onoff), 6, 7, note_lst, 0);
+	t2.lb_note_num = cui_label_new(bs2, cui_x2(t2.mn_note)+2, 6, "num : ");
+	t2.note_num = cui_num_new(bs2, cui_x2(t2.lb_note_num), 6, 8, 69, 0, 127, 0);
+	t2.lb_velo = cui_label_new(bs2, cui_x2(t2.note_num)+2, 6, "velo : ");
+	t2.velo = cui_num_new(bs2, cui_x2(t2.lb_velo), 6, 8, 64, 0, 127, 0);
+	t2.note_out = cui_button_new(bs2, cui_x2(t2.velo)+2, 6, "Out");
+	t2.bs_note_len = cui_base_new(bs2, t2.mn_note->x, 7, 0, 0);
+	t2.lb_note_len = cui_label_new(t2.bs_note_len, 0, 0, "note len : ");
+	t2.note_len = cui_num_new(t2.bs_note_len, cui_x2(t2.lb_note_len), 0, 12, 500, 0, 10*1000, 0);
+	t2.lb_ms = cui_label_new(t2.bs_note_len, cui_x2(t2.note_len)+2, 0, "msec");
+
 	for(i=1; i<ac; i++) if(strcmp(av[i], "-cmd") == 0) break;
 	if(i+1 < ac) cmd = av[i+1];
 	if((fp = popen(cmd, "w")) == NULL) ERR("popen");
@@ -139,6 +238,18 @@
 	cui_bind(quit, CUI_EVT_BUTTON, btn_hdr, prm_arr);
 	cui_bind(add, CUI_EVT_BUTTON, btn_hdr, prm_arr);
 	cui_bind(out, CUI_EVT_BUTTON, btn_hdr, prm_arr);
+
+	cui_bind(t2.prog_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
+	cui_bind(t2.vol_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
+	cui_bind(t2.note_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
+	cui_bind(t2.mn_note, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
+
+	cui_wh_fit(t2.bs_note_len);
+	cui_wh_fit(bs2);
+	cui_wh_fit(scp->view);
+	cui_wh_set(root, scp->view->w+3, scp->view->h+3);
+	cui_xy_set(scp->rszbox, root->w-1, root->h-1);
+	cui_hide(t2.bs_note_len);

 	set_str("MThd");
 	set_int(4, 6);

cui_midi.c を更新してビルドし、実行します

$ patch -p0 < cui_midi2.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui
$ ./cui_midi
========================= cui_midi ==========================
|                                                           |
|  (Quit)                                                   |
|                                                           |
|  0 | 0 |  (Add)  (Out)                                    |
|                                                           |
|                                                           |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <[50 ]>|  (Out)                             |
|                                                           |
|    vol (msb) : <[64 ]>|  (Out)                            |
|                                                           |
|    note : on    |  num : <[69 ]>|  velo : <[64 ]>|  (Out) |
|                                                           |
|                                                           |
+-----------------------------------------------------------+
上、下、prog num の Outフォーカス ENTER

これで MIDIチャンネル 0 のプログラム番号を 50 (strings ensamble 2) に設定されました

下、vol の Outフォーカス ENTER

これで MIDIチャンネル 0 のボリュームが 64 に設定されました

下、右連打で note の Outフォーカス ENTER

これで MIDIチャンネル 0 ノート番号 69、ベロシティ 64 でノートONイベントが出力されます

音が鳴りました (^_^)

左連打で note の on になってるメニューフォーカス ENTER

off を選んで ENTER

右連打でnote の Outフォーカス ENTER

これで MIDIチャンネル 0 ノート番号 69、ベロシティ 64 でノートOFFイベントが出力されます 音が止まりました

noteメニューから on/off を選ぶと

========================= cui_midi ==========================
|                                                           |
|  (Quit)                                                   |
|                                                           |
|  0 | 0 |  (Add)  (Out)                                    |
|                                                           |
|                                                           |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <[50 ]>|  (Out)                             |
|                                                           |
|    vol (msb) : <[64 ]>|  (Out)                            |
|                                                           |
|    note : on/off|  num : <[69 ]>|  velo : <[64 ]>|  (Out) |
|           note len : <[500    ]>|  msec                   |
|                                                           |
+-----------------------------------------------------------+

下に長さ 500 msec の指定が出ます

note の Outフォーカス ENTER

で、ノートONイベントを出力し、指定の長さ待機して、ノートOFFイベントを出力します

ドラムのサイドスティックの音で試してみます

ドラムなので midi ch : 9 を指定します

volの初期値は 0 なので、vol (msb) : 64 (Out) で 64 に設定します

prog numは初期値 0 のまま、変更のイベントを出力しなくても大丈夫です

note番号37が side stick なので 37 へと変更します

cui_etext へのキー入力は、ちょっとクセがあります

69 フォーカスで ENTER

で全選択状態になります

バックスペースで消去

'3' '7' とキー入力して ENTER

これで 37 が確定し、37にフォーカスした状態になります

========================= cui_midi ==========================
|                                                           |
|  (Quit)                                                   |
|                                                           |
|  0 | 0 |  (Add)  (Out)                                    |
|                                                           |
|                                                           |
|                                                           |
|  midi ch : 9  |                                           |
|                                                           |
|    prog num : <[50 ]>|  (Out)                             |
|                                                           |
|    vol (msb) : <[64 ]>|  (Out)                            |
|                                                           |
|    note : on/off|  num : <[37 ]>|  velo : <[64 ]>|  (Out) |
|           note len : <[500    ]>|  msec                   |
|                                                           |
+-----------------------------------------------------------+
note : on/off  num : 37  velo : 64

の設定で

(Out)フォーカス ENTER

「カツーン」と鳴ってます

(Out) で ENTER

で、繰り返し試せます (^_^v

これで一発目のUIに比べて、かなり操作が楽になりました

CUIから音出ししてみる (その3)

CUI14 の方で部品を改良してみたので、その機能を使ってさらに試してみます。

cui/ ディレクトリを更新しておきます。

パッチファイル

cui102_103.patch
diff -urN cui102/cui_test.c cui103/cui_test.c
--- cui102/cui_test.c	Sun Apr  6 23:00:00 2014
+++ cui103/cui_test.c	Tue Apr  8 22:00:00 2014
@@ -331,15 +331,38 @@
 num_hdr(cui obj, int evt, int val, void *prm)
 {
 	/* CUI_EVT_BUTTON */
+
+	cui_num p = (cui_num)obj;
 	cui lb_num = (cui)prm;
 	static char buf[ CUI_ETEXT_BSIZE ];

-	sprintf(buf, "%d", val);
+	if(p->use_dbl) sprintf(buf, "%f", p->dv);
+	else sprintf(buf, "%d", val);
+
+	if(p->vp) sprintf(buf + strlen(buf), " (%d)", *p->vp);
+	if(p->dvp) sprintf(buf + strlen(buf), " (%f)", *p->dvp);
+
 	cui_label_str_set(lb_num, buf);
 	return TRUE;
 }

 int
+btn_num_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	cui *arr = (cui *)prm;
+	cui num2 = arr[0];
+	cui num_d2 = arr[1];
+	static int v2 = 7;
+	static double dv2 = 45.768;
+
+	cui_num_vp_set(num2, &v2);
+	cui_num_dvp_set(num_d2, &dv2);
+	return TRUE;
+}
+
+int
 main(int ac, char **av)
 {
 	int dbg_ret = cui_dbg_open(ac, av);
@@ -361,7 +384,11 @@
 	cui num = cui_num_new(bs_num, 1, 1, 20, 0, -5, 5, 0);
 	cui num2 = cui_num_new(bs_num, 1, 3, 20, 0, -128, 127, 0);
 	cui num3 = cui_num_new(bs_num, 1, 5, 20, 0, 0, 0, 0);
-	cui lb_num = cui_label_new(bs_num, 1, 7, "");
+	cui num_d1 = cui_num_dbl_new(bs_num, 1, 7, 20, 0, -1, 1, 0.01, 0);
+	cui num_d2 = cui_num_dbl_new(bs_num, 1, 9, 20, 12.345, 0, 100, 0.1, 0);
+	cui lb_num = cui_label_new(bs_num, 1, 11, "");
+	cui btn_num = cui_button_new(bs_num, 1, 13, "addr set");
+	cui num_arr[] = { num2, num_d2 };
 	cui base = cui_base_new(bs_view, 0, 1, 40, 20);
 	cui mn_btn = cui_menu_btn_new(base, 1, 1, "File");
 	cui etx = cui_etext_new(base, mn_btn->x, cui_y2(mn_btn), 8, "foo");
@@ -416,7 +443,11 @@
 	cui_bind(num, CUI_EVT_BUTTON, num_hdr, lb_num);
 	cui_bind(num2, CUI_EVT_BUTTON, num_hdr, lb_num);
 	cui_bind(num3, CUI_EVT_BUTTON, num_hdr, lb_num);
-	cui_wh_fit(bs_num);
+	cui_bind(num_d1, CUI_EVT_BUTTON, num_hdr, lb_num);
+	cui_bind(num_d2, CUI_EVT_BUTTON, num_hdr, lb_num);
+	cui_bind(btn_num, CUI_EVT_BUTTON, btn_num_hdr, num_arr);
+	//cui_wh_fit(bs_num);
+	cui_wh_set(bs_num, bs_num->parent->w - bs_num->x, bs_num->parent->h - bs_num->y);

 	cui_tab_new(bs_view, 0, 0, -1, (char *[]){"foo", "bar", "hoge", "term", "timer", "num", NULL}, tab_sheets, 0);

diff -urN cui102/num.c cui103/num.c
--- cui102/num.c	Sun Apr  6 23:00:00 2014
+++ cui103/num.c	Tue Apr  8 22:00:00 2014
@@ -34,6 +34,7 @@
 	p->v = init_v;
 	p->min_v = min_v;
 	p->max_v = max_v;
+	p->vp = NULL;
 	sprintf(buf, "%d", p->v);

 	if(bar_h == 0) bar_h = w;
@@ -51,6 +52,8 @@
 	cui_wh_fit(p->bs_bar);
 	p->bs_bar->w++;

+	p->use_dbl = FALSE;
+
 	cui_bind(p->etx, CUI_EVT_BUTTON, cui_num_hdr, obj);
 	cui_bind(p->fb_dec, CUI_EVT_BUTTON, cui_num_hdr, obj);
 	cui_bind(p->fb_inc, CUI_EVT_BUTTON, cui_num_hdr, obj);
@@ -67,11 +70,50 @@
 	char buf[ CUI_ETEXT_BSIZE ];
 	char *s = cui_etext_str_get(p->etx);

-	if(strncmp(s, "0x", 2) == 0) sprintf(buf, "0x%x", (unsigned int)p->v);
+	if(p->use_dbl){
+		int i;
+		sprintf(buf, "%f", p->dv);
+		i = strlen(buf) - 1;
+		while(i > 0 && buf[i] == '0') buf[i--] = '\0';
+		if(buf[i] == '.') buf[i] = '\0';
+	}else if(strncmp(s, "0x", 2) == 0) sprintf(buf, "0x%x", (unsigned int)p->v);
 	else sprintf(buf, "%d", p->v);
 	cui_etext_str_set(p->etx, buf);
 }

+static void
+num_set(cui obj, int v)
+{
+	cui_num p = (cui_num)obj;
+	if(v < p->min_v) v = p->min_v;
+	if(v > p->max_v) v = p->max_v;
+	p->v = v;
+	if(p->vp) *p->vp = p->v;
+}
+
+static void
+num_dbl_set(cui obj, double dv)
+{
+	cui_num p = (cui_num)obj;
+	int v;
+
+	if(!p->use_dbl) return;
+	if(dv < p->min_dv) dv = p->min_dv;
+	if(dv > p->max_dv) dv = p->max_dv;
+	p->dv = dv;
+	if(p->dvp) *p->dvp = p->dv;
+
+	v = (int)(p->ddiv * (dv - p->min_dv) / (p->max_dv - p->min_dv));
+	num_set(obj, v);
+}
+
+static double
+to_dv(cui obj, int v)
+{
+	cui_num p = (cui_num)obj;
+	return p->min_dv + (p->max_dv - p->min_dv) * v / p->ddiv;
+}
+
 int
 cui_num_hdr(cui obj, int evt, int val, void *prm)
 {
@@ -83,22 +125,22 @@
 	if(obj == p->etx){
 		char *s = cui_etext_str_get(p->etx);
 		cui_etext_hist_del(p->etx, s);
-		p->v = strtol(s, NULL, 0);
-		if(p->v > p->max_v) p->v = p->max_v;
-		if(p->v < p->min_v) p->v = p->min_v;
-		update(num);
+		if(p->use_dbl) cui_num_dbl_set(num, strtod(s, NULL));
+		else cui_num_set(num, strtol(s, NULL, 0));
 		cui_etext_hist_add(p->etx);
 		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
 		return TRUE;
 	}else if(obj == p->fb_inc){
 		if(p->v >= p->max_v) return TRUE;
-		p->v++;
+		num_set(num, p->v + 1);
+		if(p->use_dbl) num_dbl_set(num, to_dv(num, p->v));
 		update(num);
 		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
 		return TRUE;
 	}else if(obj == p->fb_dec){
 		if(p->v <= p->min_v) return TRUE;
-		p->v--;
+		num_set(num, p->v - 1);
+		if(p->use_dbl) num_dbl_set(num, to_dv(num, p->v));
 		update(num);
 		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
 		return TRUE;
@@ -117,7 +159,8 @@
 		cui_draw(p->bs_bar->parent);
 		return TRUE;
 	}else if(obj == p->bar){
-		p->v = p->sc_max - val + p->min_v;
+		num_set(num, p->sc_max - val + p->min_v);
+		if(p->use_dbl) num_dbl_set(num, to_dv(num, p->v));
 		update(num);
 		cui_handler_call(num, CUI_EVT_BUTTON, p->v);
 		return TRUE;
@@ -126,6 +169,101 @@
 		return TRUE;
 	}
 	return FALSE;
+}
+
+int
+cui_num_get(cui obj)
+{
+	cui_num p = (cui_num)obj;
+	return p->v;
+}
+
+void
+cui_num_set(cui obj, int v)
+{
+	num_set(obj, v);
+	update(obj);
+}
+
+void
+cui_num_vp_set(cui obj, int *vp)
+{
+	cui_num p = (cui_num)obj;
+	p->vp = vp;
+	if(p->vp) cui_num_set(obj, *p->vp);
+}
+
+void
+cui_num_min_max_set(cui obj, int min_v, int max_v)
+{
+	cui_num p = (cui_num)obj;
+	p->min_v = min_v;
+	p->max_v = max_v;
+	p->sc_max = max_v - min_v;
+	cui_num_set(obj, p->v);
+}
+
+cui
+cui_num_dbl_new(cui parent, int x, int y, int w, double init_dv, double min_dv, double max_dv, double step_dv, int bar_h)
+{
+	cui obj = cui_alloc(sizeof(struct cui_num));
+	cui_num_dbl_init(obj, parent, x, y, w, init_dv, min_dv, max_dv, step_dv, bar_h);
+	return obj;
+}
+
+void
+cui_num_dbl_init(cui obj, cui parent, int x, int y, int w, double init_dv, double min_dv, double max_dv, double step_dv, int bar_h)
+{
+	cui_num p = (cui_num)obj;
+	int ddiv = (step_dv != 0) ? (int)((max_dv - min_dv) / step_dv) : 20;
+	int v = (int)(ddiv * (init_dv - min_dv) / (max_dv - min_dv));
+	cui_num_init(obj, parent, x, y, w, v, 0, ddiv, bar_h);
+	p->use_dbl = TRUE;
+	p->dv = init_dv;
+	p->min_dv = min_dv;
+	p->max_dv = max_dv;
+	p->dvp = NULL;
+	p->ddiv = ddiv;
+	cui_num_dbl_set(obj, init_dv);
+}
+
+double
+cui_num_dbl_get(cui obj)
+{
+	cui_num p = (cui_num)obj;
+	return p->use_dbl ? p->dv : 0;
+}
+
+void
+cui_num_dbl_set(cui obj, double dv)
+{
+	num_dbl_set(obj, dv);
+	update(obj);
+}
+
+void
+cui_num_dbl_vp_set(cui obj, double *dvp)
+{
+	cui_num p = (cui_num)obj;
+	p->dvp = dvp;
+	if(p->dvp) cui_num_dbl_set(obj, *p->dvp);
+}
+
+void
+cui_num_dvp_set(cui obj, double *dvp)
+{
+	cui_num_dbl_vp_set(obj, dvp);
+}
+
+void
+cui_num_dbl_min_max_set(cui obj, double min_dv, double max_dv, double step_dv)
+{
+	cui_num p = (cui_num)obj;
+	int ddiv = (step_dv != 0) ? (int)((max_dv - min_dv) / step_dv) : 20;
+	cui_num_min_max_set(obj, 0, ddiv);
+	p->min_dv = min_dv;
+	p->max_dv = max_dv;
+	cui_num_dbl_set(obj, p->dv);
 }

 /* EOF */
diff -urN cui102/num.h cui103/num.h
--- cui102/num.h	Sun Apr  6 23:00:00 2014
+++ cui103/num.h	Tue Apr  8 22:00:00 2014
@@ -7,12 +7,34 @@
 	struct cui_base base;
 	cui fb_dec, etx, fb_inc, fb_bar;
 	cui bs_bar, bar, fb_x;
-	int v, min_v, max_v;
+	int v, min_v, max_v, *vp;
 	int sc_max;
+
+	int use_dbl;
+	double dv, min_dv, max_dv, *dvp;
+	int ddiv;
+
 } *cui_num;

 cui cui_num_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v, int bar_h);
 void cui_num_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v, int bar_h);
 int cui_num_hdr(cui obj, int evt, int val, void *prm);
+
+int cui_num_get(cui obj);
+void cui_num_set(cui obj, int v);
+void cui_num_vp_set(cui obj, int *vp);
+void cui_num_min_max_set(cui obj, int min_v, int max_v);
+
+
+/* for use_dbl */
+
+cui cui_num_dbl_new(cui parent, int x, int y, int w, double init_dv, double min_dv, double max_dv, double step_dv, int bar_h);
+void cui_num_dbl_init(cui obj, cui parent, int x, int y, int w, double init_dv, double min_dv, double max_dv, double step_dv, int bar_h);
+
+double cui_num_dbl_get(cui obj);
+void cui_num_dbl_set(cui obj, double dv);
+void cui_num_dbl_vp_set(cui obj, double *dvp);
+void cui_num_dvp_set(cui obj, double *dvp);
+void cui_num_dbl_min_max_set(cui obj, double min_dv, double max_dv, double step_dv);

 #endif

cui103_104.patch
diff -urN cui103/cui.c cui104/cui.c
--- cui103/cui.c	Thu Apr  3 22:00:00 2014
+++ cui104/cui.c	Tue Apr  8 23:00:00 2014
@@ -385,6 +385,12 @@
 	if(obj->w != bak_w || obj->h != bak_h) cui_handler_call(obj, CUI_EVT_RESIZE, TRUE);
 }

+void
+cui_wh_exp(cui obj)
+{
+	if(obj->parent)	cui_wh_set(obj, obj->parent->w - obj->x, obj->parent->h - obj->y);
+}
+
 int
 cui_index(cui obj)
 {
diff -urN cui103/cui.h cui104/cui.h
--- cui103/cui.h	Thu Apr  3 22:00:00 2014
+++ cui104/cui.h	Tue Apr  8 23:00:00 2014
@@ -80,6 +80,7 @@
 void cui_wh_set(cui obj, int w, int h);
 void cui_wh_fit_chain(cui obj, cui child);
 void cui_wh_fit(cui obj);
+void cui_wh_exp(cui obj);

 int cui_index(cui obj);
 cui cui_index_to_child(cui obj, int i);
diff -urN cui103/cui_test.c cui104/cui_test.c
--- cui103/cui_test.c	Tue Apr  8 22:00:00 2014
+++ cui104/cui_test.c	Tue Apr  8 23:00:00 2014
@@ -347,6 +347,22 @@
 }

 int
+mn_int_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	cui_menu_int p = (cui_menu_int)obj;
+	cui lb_num = (cui)prm;
+	static char buf[ CUI_ETEXT_BSIZE ];
+
+	sprintf(buf, "%d", val);
+	if(p->vp) sprintf(buf + strlen(buf), " (%d)", *p->vp);
+
+	cui_label_str_set(lb_num, buf);
+	return TRUE;
+}
+
+int
 btn_num_hdr(cui obj, int evt, int val, void *prm)
 {
 	/* CUI_EVT_BUTTON */
@@ -354,11 +370,13 @@
 	cui *arr = (cui *)prm;
 	cui num2 = arr[0];
 	cui num_d2 = arr[1];
-	static int v2 = 7;
+	cui mn_int1 = arr[2];
+	static int v2 = 7, v3 = -2;
 	static double dv2 = 45.768;

 	cui_num_vp_set(num2, &v2);
 	cui_num_dvp_set(num_d2, &dv2);
+	cui_menu_int_vp_set(mn_int1, &v3);
 	return TRUE;
 }

@@ -386,9 +404,10 @@
 	cui num3 = cui_num_new(bs_num, 1, 5, 20, 0, 0, 0, 0);
 	cui num_d1 = cui_num_dbl_new(bs_num, 1, 7, 20, 0, -1, 1, 0.01, 0);
 	cui num_d2 = cui_num_dbl_new(bs_num, 1, 9, 20, 12.345, 0, 100, 0.1, 0);
-	cui lb_num = cui_label_new(bs_num, 1, 11, "");
-	cui btn_num = cui_button_new(bs_num, 1, 13, "addr set");
-	cui num_arr[] = { num2, num_d2 };
+	cui mn_int1 = cui_menu_int_new(bs_num, 1, 11, 20, 0, -5, 5);
+	cui lb_num = cui_label_new(bs_num, 1, 13, "");
+	cui btn_num = cui_button_new(bs_num, 1, 15, "addr set");
+	cui num_arr[] = { num2, num_d2, mn_int1 };
 	cui base = cui_base_new(bs_view, 0, 1, 40, 20);
 	cui mn_btn = cui_menu_btn_new(base, 1, 1, "File");
 	cui etx = cui_etext_new(base, mn_btn->x, cui_y2(mn_btn), 8, "foo");
@@ -445,9 +464,9 @@
 	cui_bind(num3, CUI_EVT_BUTTON, num_hdr, lb_num);
 	cui_bind(num_d1, CUI_EVT_BUTTON, num_hdr, lb_num);
 	cui_bind(num_d2, CUI_EVT_BUTTON, num_hdr, lb_num);
+	cui_bind(mn_int1, CUI_EVT_BUTTON, mn_int_hdr, lb_num);
 	cui_bind(btn_num, CUI_EVT_BUTTON, btn_num_hdr, num_arr);
-	//cui_wh_fit(bs_num);
-	cui_wh_set(bs_num, bs_num->parent->w - bs_num->x, bs_num->parent->h - bs_num->y);
+	cui_wh_exp(bs_num);

 	cui_tab_new(bs_view, 0, 0, -1, (char *[]){"foo", "bar", "hoge", "term", "timer", "num", NULL}, tab_sheets, 0);

diff -urN cui103/menu.c cui104/menu.c
--- cui103/menu.c	Thu Mar 27 22:00:00 2014
+++ cui104/menu.c	Tue Apr  8 23:00:00 2014
@@ -2,6 +2,7 @@
 #include "handler.h"
 #include "key.h"
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>

 cui
@@ -27,7 +28,6 @@
 	obj->w = 0;
 	cui_wh_fit(obj);
 	cui_label_attr_set(btn->lb2, CUI_ATTR_ULINE);
-	cui_label_attr_set(btn->lb3, CUI_ATTR_ULINE);
 	cui_show(obj);
 }

@@ -38,7 +38,6 @@
 	int attr = CUI_ATTR_ULINE;
 	if(val == CUI_DRAW_FOCUS) attr |= CUI_ATTR_REVERSE;
 	cui_label_attr_set(btn->lb2, attr);
-	cui_label_attr_set(btn->lb3, attr);
 	return TRUE;
 }

@@ -71,7 +70,6 @@
 	cui_hide(obj);
 	p->lbf = cui_lblfix_new(obj, 0, 0, -1, s, CUI_ATTR_ULINE);
 	p->lb_end = cui_label_new(obj, cui_x2(p->lbf), 0, "|");
-	cui_label_attr_set(p->lb_end, CUI_ATTR_ULINE);
 	cui_bind(obj, CUI_EVT_DRAW | CUI_EVT_KEY | CUI_EVT_RESIZE, cui_menu_item_hdr, NULL);
 	cui_wh_fit(obj);
 	cui_show(obj);
@@ -110,7 +108,6 @@
 		attr = CUI_ATTR_ULINE;
 		if(val == CUI_DRAW_FOCUS) attr |= CUI_ATTR_REVERSE;
 		cui_lblfix_attr_set(p->lbf, attr);
-		cui_label_attr_set(p->lb_end, attr);
 		return TRUE;
 	case CUI_EVT_RESIZE:
 		cui_w_set(p->lbf, obj->w - 1);
@@ -182,6 +179,7 @@
 	if(w >= 0) cui_w_set(obj, w);
 	p->menu = cui_menu_new(cui_root(obj), 0, 0, lst);
 	p->val = val;
+	p->call_btn_hdr_flag = TRUE; /* ! */

 	cui_unbind(obj, CUI_EVT_KEY | CUI_EVT_BUTTON, cui_menu_item_hdr);
 	cui_bind(obj, CUI_EVT_KEY, cui_menu_popup_hdr, NULL);
@@ -204,7 +202,7 @@
 		else{
 			char *s = cui_menu_item_str_get(cui_index_to_child(menu, p->val));
 			cui_menu_item_str_set(obj, s);
-			cui_handler_call(obj, CUI_EVT_BUTTON, p->val);
+			if(p->call_btn_hdr_flag) cui_handler_call(obj, CUI_EVT_BUTTON, p->val);
 		}
 		cui_draw(menu->parent);
 		return TRUE;
@@ -221,6 +219,160 @@
 cui_menu_popup_str_get(cui obj)
 {
 	return cui_menu_item_str_get(obj);
+}
+
+int
+cui_menu_popup_get(cui obj)
+{
+	cui_menu_popup p = (cui_menu_popup)obj;
+	return p->val;
+}
+
+void
+cui_menu_popup_set(cui obj, int idx)
+{
+	cui_menu_popup p = (cui_menu_popup)obj;
+	char *s;
+	p->val = idx;
+	s = cui_menu_item_str_get(cui_index_to_child(p->menu, p->val));
+	cui_menu_item_str_set(obj, s);
+}
+
+cui
+cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v)
+{
+	cui obj = cui_alloc(sizeof(struct cui_menu_int));
+	cui_menu_int_init(obj, parent, x, y, w, init_v, min_v, max_v);
+	return obj;
+}
+
+static int
+menu_int_alloc_size(int min_v, int max_v)
+{
+	char buf[32];
+	int n = max_v - min_v + 1;
+	int sum = sizeof(char *) * (n + 1);
+	int v, i;
+
+	for(i=0; i<n; i++){
+		v = max_v - i;
+		sprintf(buf, "%d", v);
+		sum += strlen(buf) + 1;
+	}
+	return sum;
+}
+
+static void
+menu_int_lst_setup(cui obj)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+	int n = p->max_v - p->min_v + 1;
+	char *cp = (char *)&p->lst[n + 1];
+	int i, v;
+
+	for(i=0; i<n; i++){
+		p->lst[i] = cp;
+		v = p->max_v - i;
+		sprintf(cp, "%d", v);
+		cp += strlen(cp) + 1;
+	}
+	p->lst[i] = NULL;
+}
+
+void
+cui_menu_int_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+
+	if((p->alloc = malloc(menu_int_alloc_size(min_v, max_v))) == NULL) ERR("No Mem");
+	p->lst = (char **)p->alloc;
+	p->min_v = min_v;
+	p->max_v = max_v;
+	menu_int_lst_setup(obj);
+	p->v = init_v;
+	p->vp = NULL;
+
+	cui_menu_popup_init(obj, parent, x, y, w, p->lst, max_v - init_v);
+	p->popup.call_btn_hdr_flag = FALSE; /* ! */
+
+	cui_unbind(obj, CUI_EVT_KEY, cui_menu_popup_hdr);
+	cui_bind(obj, CUI_EVT_KEY, cui_menu_int_hdr, NULL);
+}
+
+void
+cui_menu_int_lst_free(cui obj)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+	if(p->alloc){
+		free(p->alloc);
+		p->alloc = NULL;
+	}
+}
+
+static void
+menu_int_set(cui obj , int v)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+	if(v < p->min_v) v = p->min_v;
+	if(v > p->max_v) v = p->max_v;
+	p->v = v;
+	if(p->vp) *p->vp = p->v;
+}
+
+int
+cui_menu_int_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_KEY */
+
+	cui_menu_int p = (cui_menu_int)obj;
+	int bak = p->popup.val;
+	if(cui_menu_popup_hdr(obj, evt, val, prm)){
+		if(p->popup.val != bak){
+			menu_int_set(obj, p->max_v - p->popup.val);
+			cui_handler_call(obj, CUI_EVT_BUTTON, p->v);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+int
+cui_menu_int_get(cui obj)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+	return p->v;
+}
+
+void
+cui_menu_int_set(cui obj, int v)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+	menu_int_set(obj, v);
+	cui_menu_popup_set(obj, p->max_v - p->v);
+}
+
+void
+cui_menu_int_vp_set(cui obj, int *vp)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+	p->vp = vp;
+	if(p->vp) cui_menu_int_set(obj, *p->vp);
+}
+
+void
+cui_menu_int_min_max_set(cui obj, int min_v, int max_v)
+{
+	cui_menu_int p = (cui_menu_int)obj;
+
+	cui_menu_int_lst_free(obj);
+
+	if((p->alloc = malloc(menu_int_alloc_size(min_v, max_v))) == NULL) ERR("No Mem");
+	p->lst = (char **)p->alloc;
+
+	p->min_v = min_v;
+	p->max_v = max_v;
+	menu_int_lst_setup(obj);
+	cui_menu_int_set(obj, p->v);
 }

 /* EOF */
diff -urN cui103/menu.h cui104/menu.h
--- cui103/menu.h	Sun Feb  9 00:00:00 2014
+++ cui104/menu.h	Tue Apr  8 23:00:00 2014
@@ -17,7 +17,15 @@
 	struct cui_menu_item item;
 	cui menu;
 	int val;
-} * cui_menu_popup;
+	int call_btn_hdr_flag; /* ! */
+} *cui_menu_popup;
+
+typedef struct cui_menu_int{
+	struct cui_menu_popup popup;
+	void *alloc;
+	char **lst;
+	int v, min_v, max_v, *vp;
+} *cui_menu_int;

 cui cui_menu_btn_new(cui parent, int x, int y, char *s);
 void cui_menu_btn_init(cui obj, cui parent, int x, int y, char *s);
@@ -38,5 +46,16 @@
 void cui_menu_popup_init(cui obj, cui parent, int x, int y, int w, char **lst, int val);
 int cui_menu_popup_hdr(cui obj, int evt, int val, void *prm);
 char *cui_menu_popup_str_get(cui obj);
+int cui_menu_popup_get(cui obj);
+void cui_menu_popup_set(cui obj, int idx);
+
+cui cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v);
+void cui_menu_int_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v);
+int cui_menu_int_hdr(cui obj, int evt, int val, void *prm);
+void cui_menu_int_lst_free(cui obj);
+int cui_menu_int_get(cui obj);
+void cui_menu_int_set(cui obj, int v);
+void cui_menu_int_vp_set(cui obj, int *vp);
+void cui_menu_int_min_max_set(cui obj, int min_v, int max_v);

 #endif
を引っ張ってきます

$ (cd cui ; make clean)
$ cat cui102_103.patch cui103_104.patch | (cd cui ; patch -p1)
$ (cd cui ; make lib)

まず、ほとんどそのままで、画面をタブシートで分けておきます

cui_midi.c の差分です

cui_midi3.patch
--- cui_midi.c-	Mon Apr  7 00:00:00 2014
+++ cui_midi.c	Wed Apr  9 00:00:00 2014
@@ -8,6 +8,7 @@
 #include "cui/handler.h"
 #include "cui/menu.h"
 #include "cui/num.h"
+#include "cui/tab.h"

 typedef struct hex_in{
 	struct cui_base base;
@@ -188,15 +189,15 @@
 	cui_scpanel scp = (cui_scpanel)root;
 	cui quit = cui_button_new(scp->view, 2, 1, "Quit");

-	int y = cui_y2(quit) + 1;
-	cui hxi = hex_in_new(scp->view, 2, y, 0);
-	cui add = cui_button_new(scp->view, cui_x2(hxi)+2, y, "Add");
-	cui out = cui_button_new(scp->view, cui_x2(add)+2, y, "Out");
+	cui bs1 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
+	cui hxi = hex_in_new(bs1, 0, 0, 0);
+	cui add = cui_button_new(bs1, cui_x2(hxi)+2, 0, "Add");
+	cui out = cui_button_new(bs1, cui_x2(add)+2, 0, "Out");
 	char lb_buf[ BUFN ];
-	cui lb = cui_label_new(scp->view, 2, cui_y2(hxi)+1, "");
+	cui lb = cui_label_new(bs1, 2, cui_y2(hxi)+1, "");
 	cui prm_arr[] = { hxi, lb };

-	cui bs2 = cui_base_new(scp->view, 2, cui_y2(lb)+1, 0, 0);
+	cui bs2 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
 	char *ch_lst[] = {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15",NULL};
 	char *note_lst[] = {"on", "off", "on/off", NULL};
 	struct test2 t2;
@@ -205,6 +206,10 @@
 	int div = 96;
 	int i;

+	cui tab_sheets[] = { bs1, bs2 };
+	cui_hide(bs1);
+	cui_hide(bs2);
+
 	t2.lb_ch = cui_label_new(bs2, 0, 0, "midi ch : ");
 	t2.mn_ch = cui_menu_popup_new(bs2, cui_x2(t2.lb_ch), 0, 4, ch_lst, 0);
 	t2.lb_prog = cui_label_new(bs2, 2, 2, "prog num : ");
@@ -235,6 +240,11 @@
 	lb_buf[0] = '\0';
 	cui_label_str_set(lb, lb_buf);

+	cui_show(bs2);
+	cui_tab_new(scp->view, cui_x2(quit)+2, quit->y, -1,
+		    (char *[]){"1st ", "2nd ", NULL},
+		    tab_sheets, 1);
+
 	cui_bind(quit, CUI_EVT_BUTTON, btn_hdr, prm_arr);
 	cui_bind(add, CUI_EVT_BUTTON, btn_hdr, prm_arr);
 	cui_bind(out, CUI_EVT_BUTTON, btn_hdr, prm_arr);
@@ -244,9 +254,11 @@
 	cui_bind(t2.note_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
 	cui_bind(t2.mn_note, CUI_EVT_BUTTON, t2_btn_hdr, &t2);

+	cui_wh_fit(bs1);
 	cui_wh_fit(t2.bs_note_len);
 	cui_wh_fit(bs2);
 	cui_wh_fit(scp->view);
+	cui_wh_exp(bs1);
 	cui_wh_set(root, scp->view->w+3, scp->view->h+3);
 	cui_xy_set(scp->rszbox, root->w-1, root->h-1);
 	cui_hide(t2.bs_note_len);

cui_midi.c を更新してビルドし、実行します

$ patch -p0 < cui_midi3.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui
$ ./cui_midi
========================= cui_midi ==========================
|                                                           |
|  (Quit)  /1st |/2nd |                                     |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <[50 ]>|  (Out)                             |
|                                                           |
|    vol (msb) : <[64 ]>|  (Out)                            |
|                                                           |
|    note : on    |  num : <[69 ]>|  velo : <[64 ]>|  (Out) |
|                                                           |
|                                                           |
+-----------------------------------------------------------+

1st にフォーカスして ENTER で、 一発目に試したバージョンの画面が表示されます

========================= cui_midi ==========================
|                                                           |
|  (Quit)  /1st |/2nd |                                     |
|                                                           |
|  0 | 0 |  (Add)  (Out)                                    |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
|                                                           |
+-----------------------------------------------------------+

2nd フォーカス ENTER で戻って、 以前と同様の操作で、同様に音が鳴ります

OK

Quitフォーカス ENTER で終了

それでは第3段

基本的に第2段と同じ画面で試します

midi ch を切替えたら、そのチャンネルの状態を覚えていて、 UIの表示に反映するようにします

できるだけ(Out)ボタンをなくして、 数値を変更したら、すぐイベントを出力するようにしてみます

cui_midi.c の差分です

cui_midi4.patch
--- cui_midi.c-	Wed Apr  9 00:00:00 2014
+++ cui_midi.c	Wed Apr  9 01:00:00 2014
@@ -181,6 +181,60 @@
 	return FALSE;
 }

+struct test3_ch{
+	int prog, vol;
+	int note, note_num, velo, note_len;
+};
+
+struct test3{
+	cui mn_ch, prog, vol;
+	cui mn_note, note_num, velo, note_out;
+	cui bs_note_len, note_len;
+
+	int midi_ch;
+	struct test3_ch ch[16];
+};
+
+int
+t3_btn_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	struct test3 *p = (struct test3 *)prm;
+	struct test3_ch *ch = &p->ch[ p->midi_ch ];
+
+	if(obj == p->mn_ch){
+		cui_num_vp_set(p->prog, &ch->prog);
+		cui_num_vp_set(p->vol, &ch->vol);
+		cui_menu_popup_set(p->mn_note, ch->note); /* ! */
+		cui_handler_call(p->mn_note, CUI_EVT_BUTTON, ch->note); /* ! */
+		cui_num_vp_set(p->note_num, &ch->note_num);
+		cui_num_vp_set(p->velo, &ch->velo);
+		cui_num_vp_set(p->note_len, &ch->note_len);
+	}else if(obj == p->prog){
+		set_char2_wrt((0xc<<4) | p->midi_ch, ch->prog);
+	}else if(obj == p->vol){
+		set_char3_wrt((0xb<<4) | p->midi_ch, 7 /* vol msb */, ch->vol);
+	}else if(obj == p->mn_note){ /* ! */
+		char *s = cui_menu_popup_str_get(p->mn_note);
+		if(strcmp(s, "on/off") == 0) cui_show(p->bs_note_len);
+		else cui_hide(p->bs_note_len);
+		ch->note = cui_menu_popup_get(p->mn_note);
+	}else if(obj == p->note_out){
+		char *s = cui_menu_popup_str_get(p->mn_note);
+		if(strcmp(s, "on") == 0){
+			set_char3_wrt((9<<4) | p->midi_ch, ch->note_num, ch->velo);
+		}else if(strcmp(s, "off") == 0){
+			set_char3_wrt((8<<4) | p->midi_ch, ch->note_num, ch->velo);
+		}else if(strcmp(s, "on/off") == 0){
+			set_char3_wrt((9<<4) | p->midi_ch, ch->note_num, ch->velo);
+			usleep(ch->note_len * 1000);
+			set_char3_wrt((8<<4) | p->midi_ch, ch->note_num, ch->velo);
+		}else return FALSE;
+	}else return FALSE;
+	return TRUE;
+}
+
 int
 main(int ac, char **av)
 {
@@ -202,13 +256,29 @@
 	char *note_lst[] = {"on", "off", "on/off", NULL};
 	struct test2 t2;

+	cui bs3 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
+	struct test3 t3;
+	cui obj;
+
 	char *cmd = "./add_delta | ./prog31 -V0 -q -play";
 	int div = 96;
 	int i;

-	cui tab_sheets[] = { bs1, bs2 };
+	cui tab_sheets[] = { bs1, bs2, bs3 };
 	cui_hide(bs1);
 	cui_hide(bs2);
+	cui_hide(bs3);
+
+	lb_buf[0] = '\0';
+	cui_label_str_set(lb, lb_buf);
+
+	cui_wh_fit(bs1);
+
+	cui_bind(quit, CUI_EVT_BUTTON, btn_hdr, prm_arr);
+	cui_bind(add, CUI_EVT_BUTTON, btn_hdr, prm_arr);
+	cui_bind(out, CUI_EVT_BUTTON, btn_hdr, prm_arr);
+
+	/* t2 */

 	t2.lb_ch = cui_label_new(bs2, 0, 0, "midi ch : ");
 	t2.mn_ch = cui_menu_popup_new(bs2, cui_x2(t2.lb_ch), 0, 4, ch_lst, 0);
@@ -230,38 +300,80 @@
 	t2.note_len = cui_num_new(t2.bs_note_len, cui_x2(t2.lb_note_len), 0, 12, 500, 0, 10*1000, 0);
 	t2.lb_ms = cui_label_new(t2.bs_note_len, cui_x2(t2.note_len)+2, 0, "msec");

-	for(i=1; i<ac; i++) if(strcmp(av[i], "-cmd") == 0) break;
-	if(i+1 < ac) cmd = av[i+1];
-	if((fp = popen(cmd, "w")) == NULL) ERR("popen");
-
-	for(i=1; i<ac; i++) if(strcmp(av[i], "-div") == 0) break;
-	if(i+1 < ac) div = strtol(av[i+1], NULL, 0);
-
-	lb_buf[0] = '\0';
-	cui_label_str_set(lb, lb_buf);
-
-	cui_show(bs2);
-	cui_tab_new(scp->view, cui_x2(quit)+2, quit->y, -1,
-		    (char *[]){"1st ", "2nd ", NULL},
-		    tab_sheets, 1);
-
-	cui_bind(quit, CUI_EVT_BUTTON, btn_hdr, prm_arr);
-	cui_bind(add, CUI_EVT_BUTTON, btn_hdr, prm_arr);
-	cui_bind(out, CUI_EVT_BUTTON, btn_hdr, prm_arr);
-
 	cui_bind(t2.prog_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
 	cui_bind(t2.vol_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
 	cui_bind(t2.note_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
 	cui_bind(t2.mn_note, CUI_EVT_BUTTON, t2_btn_hdr, &t2);

-	cui_wh_fit(bs1);
+	cui_hide(t2.bs_note_len);
 	cui_wh_fit(t2.bs_note_len);
 	cui_wh_fit(bs2);
+
+	/* t3 */
+
+	t3.midi_ch = 0;
+	for(i=0; i<16; i++){
+		t3.ch[i].prog = 0;
+		t3.ch[i].vol = 0;
+		t3.ch[i].note = 0;
+		t3.ch[i].note_num = 69;
+		t3.ch[i].velo = 64;
+		t3.ch[i].note_len = 500;
+	}
+	obj = cui_label_new(bs3, 0, 0, "midi ch : ");
+	t3.mn_ch = cui_menu_int_new(bs3, cui_x2(obj), 0, 4, 0, 0, 15);
+	cui_menu_int_vp_set(t3.mn_ch, &t3.midi_ch);
+
+	obj = cui_label_new(bs3, 2, 2, "prog num : ");
+	t3.prog = cui_num_new(bs3, cui_x2(obj), 2, 8, 0, 0, 127, 0);
+
+	obj = cui_label_new(bs3, 2, 4, "vol (msb) : ");
+	t3.vol = cui_num_new(bs3, cui_x2(obj), 4, 8, 0, 0, 127, 0);
+
+	obj = cui_label_new(bs3, 2, 6, "note : ");
+	t3.mn_note = cui_menu_popup_new(bs3, cui_x2(obj), 6, 7, note_lst, 0);
+
+	obj = cui_label_new(bs3, cui_x2(t3.mn_note)+2, 6, "num : ");
+	t3.note_num = cui_num_new(bs3, cui_x2(obj), 6, 8, 0, 0, 127, 0);
+	obj = cui_label_new(bs3, cui_x2(t3.note_num)+2, 6, "velo : ");
+	t3.velo = cui_num_new(bs3, cui_x2(obj), 6, 8, 0, 0, 127, 0);
+	t3.note_out = cui_button_new(bs3, cui_x2(t3.velo)+2, 6, "Out");
+
+	t3.bs_note_len = cui_base_new(bs3, t3.mn_note->x, 7, 0, 0);
+	obj = cui_label_new(t3.bs_note_len, 0, 0, "note len : ");
+	t3.note_len = cui_num_new(t3.bs_note_len, cui_x2(obj), 0, 12, 0, 0, 10*1000, 0);
+	cui_label_new(t3.bs_note_len, cui_x2(t3.note_len)+2, 0, "msec");
+
+	cui_hide(t3.bs_note_len);
+	cui_wh_fit(t3.bs_note_len);
+	cui_wh_fit(bs3);
+
+	cui_bind(t3.mn_ch, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
+	cui_bind(t3.prog, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
+	cui_bind(t3.vol, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
+	cui_bind(t3.note_out, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
+	cui_bind(t3.mn_note, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
+
+	cui_handler_call(t3.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t3.mn_ch));
+
+	/**/
+
+	cui_show(bs3);
+	cui_tab_new(scp->view, cui_x2(quit)+2, quit->y, -1,
+		    (char *[]){"1st ", "2nd ", "3rd ", NULL},
+		    tab_sheets, 2);
+
 	cui_wh_fit(scp->view);
 	cui_wh_exp(bs1);
 	cui_wh_set(root, scp->view->w+3, scp->view->h+3);
 	cui_xy_set(scp->rszbox, root->w-1, root->h-1);
-	cui_hide(t2.bs_note_len);
+
+	for(i=1; i<ac; i++) if(strcmp(av[i], "-cmd") == 0) break;
+	if(i+1 < ac) cmd = av[i+1];
+	if((fp = popen(cmd, "w")) == NULL) ERR("popen");
+
+	for(i=1; i<ac; i++) if(strcmp(av[i], "-div") == 0) break;
+	if(i+1 < ac) div = strtol(av[i+1], NULL, 0);

 	set_str("MThd");
 	set_int(4, 6);

cui_midi.c を更新してビルドし、実行します

$ patch -p0 < cui_midi4.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui
$ ./cui_midi
========================= cui_midi ==========================
|                                                           |
|  (Quit)  /1st |/2nd |/3rd |                               |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <[0  ]>|                                    |
|                                                           |
|    vol (msb) : <[0  ]>|                                   |
|                                                           |
|    note : on    |  num : <[69 ]>|  velo : <[64 ]>|  (Out) |
|                                                           |
|                                                           |
+-----------------------------------------------------------+

動作確認

まずch 0でストリングスから

prog num を 50 に設定
vol を 64 に設定
note を on/off に設定
(Out)フォーカス ENTER
========================= cui_midi ==========================
|                                                           |
|  (Quit)  /1st |/2nd |/3rd |                               |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <[50 ]>|                                    |
|                                                           |
|    vol (msb) : <[64 ]>|                                   |
|                                                           |
|    note : on/off|  num : <[69 ]>|  velo : <[64 ]>|  (Out) |
|           note len : <[500    ]>|  msec                   |
|                                                           |
+-----------------------------------------------------------+

OK 音が鳴ります

続いてドラムのサイドスティック

midi ch を 9 に設定
vol を 64 に設定
note を on/off に設定
num を 37 に設定
(Out)フォーカス ENTER
========================= cui_midi ==========================
|                                                           |
|  (Quit)  /1st |/2nd |/3rd |                               |
|                                                           |
|  midi ch : 9  |                                           |
|                                                           |
|    prog num : <[0  ]>|                                    |
|                                                           |
|    vol (msb) : <[64 ]>|                                   |
|                                                           |
|    note : on/off|  num : <[37 ]>|  velo : <[64 ]>|  (Out) |
|           note len : <[500    ]>|  msec                   |
|                                                           |
+-----------------------------------------------------------+

OK 音が鳴ります

midi ch を 0 に戻してみると、他の設定も ch 0 の状態に戻ります (^_^)

========================= cui_midi ==========================
|                                                           |
|  (Quit)  /1st |/2nd |/3rd |                               |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <[50 ]>|                                    |
|                                                           |
|    vol (msb) : <[64 ]>|                                   |
|                                                           |
|    note : on/off|  num : <[69 ]>|  velo : <[64 ]>|  (Out) |
|           note len : <[500    ]>|  msec                   |
|                                                           |
+-----------------------------------------------------------+

CUIから音出ししてみる (その4)

こうなってくるとnote番号の指定は、 やはり鍵盤の部品で操作したくなってきます。

という事でチャレンジしてみます。

試してみて、うまくいけば CUI のライブラリに追加する事にします。

CUI14 の方に更新が入ってるので、先に反映しておきます。

次のパッチファイル を引っ張ってきます。

cui104_105.patch
diff -urN cui104/cui.c cui105/cui.c
--- cui104/cui.c	Tue Apr  8 23:00:00 2014
+++ cui105/cui.c	Thu Apr 10 22:00:00 2014
@@ -9,6 +9,8 @@
 #include "button.h"
 #include "timer.h"
 #include "list.h"
+#include "menu.h"
+#include "term.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -129,9 +131,8 @@
 	cui_esc_loc(cui_gx(obj) + x, cui_gy(obj) + y);
 	if(attr & CUI_ATTR_ULINE) cui_esc_attr(CUI_ESC_ULINE);
 	if(attr & CUI_ATTR_REVERSE) cui_esc_attr(CUI_ESC_REVERSE);
-	printf("%s", s);
+	cui_puts(s);
 	if(attr != CUI_ATTR_NORMAL) cui_esc_attr(CUI_ESC_NORMAL);
-	fflush(stdout);
 	if(s2) free(s2);
 }

@@ -520,6 +521,105 @@
 	}
 }

+static void
+html_puts(char *s, void *prm)
+{
+	cui term = (cui)prm;
+	cui_term_puts_buf(term, s);
+}
+
+static void
+html_buf_flush(cui obj, int *attr, char *lp, int x, int y)
+{
+	cui_term p = (cui_term)obj;
+	char *s = p->lbuf, c;
+
+	if(*attr == -1) return;
+	*lp = '\0';
+
+	if(*attr & CUI_ATTR_ULINE) cui_dbg("<u>");
+	if(*attr & CUI_ATTR_REVERSE) cui_dbg("<font color=\"#FFFFFF\" style=\"background-color:#000000\">");
+	while((c = *s++) != '\0'){
+		switch(c){
+		case '&': cui_dbg("&amp;"); break;
+		case '<': cui_dbg("&lt;"); break;
+		case '>': cui_dbg("&gt;"); break;
+		default: cui_dbg("%c", c); break;
+		}
+	}
+	if(*attr & CUI_ATTR_REVERSE) cui_dbg("</font>");
+	if(*attr & CUI_ATTR_ULINE) cui_dbg("</u>");
+	*attr = -1;
+}
+
+static void
+html_buf_out(cui obj)
+{
+	cui_term p = (cui_term)obj;
+	char *lp = p->lbuf;
+	int x, y, attr = -1, lx = 0;
+
+	cui_dbg("<pre>\n");
+	for(y=0; y<obj->h; y++){
+		for(x=0; x<obj->w; x++){
+			char *bp = cui_term_buf(obj, x, y);
+			if(attr != -1 && bp[1] != attr) html_buf_flush(obj, &attr, lp, lx, y);
+			if(attr == -1){
+				lp = p->lbuf;
+				*lp++ = bp[0] ? bp[0] : ' ';
+				attr = bp[1];
+				lx = x;
+				continue;
+			}
+			/* bp[1] == attr */
+			*lp++ = bp[0] ? bp[0] : ' ';
+			continue;
+			html_buf_flush(obj, &attr, lp, lx, y);
+		}
+		html_buf_flush(obj, &attr, lp, lx, y);
+		cui_dbg("\n");
+	}
+	cui_dbg("</pre>\n");
+}
+
+static void
+html(cui obj)
+{
+	cui term = cui_term_new(obj, 0, 0, cui_x2(obj)+2, cui_y2(obj)+2);
+	cui_hide(term);
+
+	cui_puts_add(html_puts, term);
+	cui_draw(obj);
+
+	cui_puts_del(html_puts);
+
+	html_buf_out(term);
+
+	cui_term_buf_free(term);
+	cui_del_free(term);
+}
+
+static int
+cmenu(cui obj, int key)
+{
+	cui mn;
+	char *lst[] = { "Cancel", "^C", "HTML", "Quit", NULL }, *s;
+
+	if(key != CUI_KEY_CTRL('C')) return key;
+
+	mn = cui_menu_popup_new(obj, 0, 1, -1, lst, 1);
+	cui_hide(mn);
+	cui_handler_call(mn, CUI_EVT_KEY, CUI_KEY_ENTER);
+	s = cui_menu_popup_str_get(mn);
+	cui_menu_popup_del_free(mn);
+
+	if(strcmp(s, "^C") == 0) return 1;
+	else if(strcmp(s, "Quit") == 0) cui_quit();
+	else if(strcmp(s, "HTML") == 0) html(obj);
+	/* else Cancel */
+	return 0;
+}
+
 void
 cui_main(cui top_obj, cui init_focus)
 {
@@ -548,6 +648,7 @@
 		cui_readable_work();
 		if(!cui_select_chk(KEY_FD)) continue;
 		if((key = cui_key_get2()) == 0) continue;
+		if(cmenu(cui_root(top_obj), key) == 0) continue;
 		focus = cui_focus_get();
 		if(focus){
 			if(cui_handler_call(focus, CUI_EVT_KEY, key)) continue;
@@ -597,6 +698,54 @@
 	cui_free_chain(obj->children);
 	cui_list_del_all(&obj->handler_list);
 	free(obj);
+}
+
+void
+cui_del_free(cui obj)
+{
+	cui_del(obj);
+	cui_free(obj);
+}
+
+typedef void (*puts_func)(char *, void *prm);
+
+typedef struct puts_data{
+	puts_func f;
+	void *prm;
+} *puts_data;
+
+static cui_list puts_list = NULL;
+
+void
+cui_puts_add(puts_func f, void *prm)
+{
+	puts_data p = cui_list_add(&puts_list, sizeof(struct puts_data));
+	p->f = f;
+	p->prm = prm;
+}
+
+void
+cui_puts_del(puts_func f)
+{
+	cui_list lp = puts_list;
+	for(; lp; lp=lp->next) if(*(puts_func *)lp->data == f) break;
+	if(lp == NULL) return;
+	cui_list_del(&puts_list, lp->data);
+}
+
+void
+cui_puts(char *s)
+{
+	cui_list lp = puts_list;
+	if(puts_list == NULL){
+		printf("%s", s);
+		fflush(stdout);
+		return;
+	}
+	for(; lp; lp=lp->next){
+		puts_data p = (puts_data)lp->data;
+		(*p->f)(s, p->prm);
+	}
 }

 /* EOF */
diff -urN cui104/cui.h cui105/cui.h
--- cui104/cui.h	Tue Apr  8 23:00:00 2014
+++ cui105/cui.h	Thu Apr 10 22:00:00 2014
@@ -100,5 +100,11 @@
 void cui_del(cui obj);
 void cui_free_chain(cui obj);
 void cui_free(cui obj);
+void cui_del_free(cui obj);
+
+typedef void (*puts_func)(char *, void *prm);
+void cui_puts_add(puts_func f, void *prm);
+void cui_puts_del(puts_func f);
+void cui_puts(char *s);

 #endif
diff -urN cui104/cui_test.c cui105/cui_test.c
--- cui104/cui_test.c	Tue Apr  8 23:00:00 2014
+++ cui105/cui_test.c	Thu Apr 10 22:00:00 2014
@@ -65,8 +65,7 @@
 	cui_unbind(obj, CUI_EVT_BUTTON, my_hdr);
 	joke = joke_new(((cui)p)->parent->parent->parent, 0, 4);
 	cui_main(joke, joke->children->next);
-	cui_del(joke);
-	cui_free(joke);
+	cui_del_free(joke);
 	return TRUE;
 }

@@ -235,8 +234,7 @@
 	cui_main(menu, cui_menu_str_to_item(menu, "Open"));
 	cui_hide(menu);
 	cui_draw(menu->parent);
-	cui_del(menu);
-	cui_free(menu);
+	cui_del_free(menu);

 	if(strcmp(cui_label_str_get((cui)prm), "Quit") == 0) cui_quit();
 	return TRUE;
diff -urN cui104/esc.c cui105/esc.c
--- cui104/esc.c	Fri Jan 31 01:10:00 2014
+++ cui105/esc.c	Thu Apr 10 22:00:00 2014
@@ -1,12 +1,13 @@
 #include "esc.h"
-
+#include "cui.h"
 #include <stdio.h>

 void
 cui_esc(char *s)
 {
-	printf("\033[%s", s);
-	fflush(stdout);
+	char buf[1024];
+	sprintf(buf, "\033[%s", s);
+	cui_puts(buf);
 }

 void
diff -urN cui104/etext.c cui105/etext.c
--- cui104/etext.c	Sun Feb  2 22:30:00 2014
+++ cui105/etext.c	Thu Apr 10 22:00:00 2014
@@ -140,8 +140,7 @@
 	cui_bind(pm, CUI_EVT_BUTTON, cui_etext_hist_menu_hdr, p);
 	cui_hide(pm);
 	cui_handler_call(pm, CUI_EVT_KEY, CUI_KEY_ENTER);
-	cui_del(pm);
-	cui_free(pm);
+	cui_menu_popup_del_free(pm);
 }

 int
@@ -168,8 +167,7 @@
 	cui_bind(pm, CUI_EVT_BUTTON, cui_etext_digit_menu_hdr, p);
 	cui_hide(pm);
 	cui_handler_call(pm, CUI_EVT_KEY, CUI_KEY_ENTER);
-	cui_del(pm);
-	cui_free(pm);
+	cui_menu_popup_del_free(pm);
 }

 void
diff -urN cui104/menu.c cui105/menu.c
--- cui104/menu.c	Tue Apr  8 23:00:00 2014
+++ cui105/menu.c	Thu Apr 10 22:00:00 2014
@@ -238,6 +238,14 @@
 	cui_menu_item_str_set(obj, s);
 }

+void
+cui_menu_popup_del_free(cui obj)
+{
+	cui_menu_popup p = (cui_menu_popup)obj;
+	cui_del_free(p->menu);
+	cui_del_free(obj);
+}
+
 cui
 cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v)
 {
diff -urN cui104/menu.h cui105/menu.h
--- cui104/menu.h	Tue Apr  8 23:00:00 2014
+++ cui105/menu.h	Thu Apr 10 22:00:00 2014
@@ -48,6 +48,7 @@
 char *cui_menu_popup_str_get(cui obj);
 int cui_menu_popup_get(cui obj);
 void cui_menu_popup_set(cui obj, int idx);
+void cui_menu_popup_del_free(cui obj);

 cui cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v);
 void cui_menu_int_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v);

cui105_106.patch
diff -urN cui105/cui.c cui106/cui.c
--- cui105/cui.c	2014-04-10 22:00:00.000000000 +0900
+++ cui106/cui.c	2014-04-11 02:00:00.000000000 +0900
@@ -585,7 +585,7 @@
 static void
 html(cui obj)
 {
-	cui term = cui_term_new(obj, 0, 0, cui_x2(obj)+2, cui_y2(obj)+2);
+	cui term = cui_term_new(obj, 0, 0, cui_x2(obj), cui_y2(obj)+1);
 	cui_hide(term);

 	cui_puts_add(html_puts, term);
@@ -603,7 +603,7 @@
 cmenu(cui obj, int key)
 {
 	cui mn;
-	char *lst[] = { "Cancel", "^C", "HTML", "Quit", NULL }, *s;
+	char *lst[] = { "Cancel", "^C", "Redraw", "HTML", "Quit", NULL }, *s;

 	if(key != CUI_KEY_CTRL('C')) return key;

@@ -615,7 +615,8 @@

 	if(strcmp(s, "^C") == 0) return 1;
 	else if(strcmp(s, "Quit") == 0) cui_quit();
-	else if(strcmp(s, "HTML") == 0) html(obj);
+	else if(strcmp(s, "Redraw") == 0) cui_draw(obj);
+	else if(strcmp(s, "HTML") == 0) html(cui_root(obj));
 	/* else Cancel */
 	return 0;
 }
@@ -648,7 +649,7 @@
 		cui_readable_work();
 		if(!cui_select_chk(KEY_FD)) continue;
 		if((key = cui_key_get2()) == 0) continue;
-		if(cmenu(cui_root(top_obj), key) == 0) continue;
+		if(cmenu(top_obj, key) == 0) continue;
 		focus = cui_focus_get();
 		if(focus){
 			if(cui_handler_call(focus, CUI_EVT_KEY, key)) continue;
@@ -707,8 +708,6 @@
 	cui_free(obj);
 }

-typedef void (*puts_func)(char *, void *prm);
-
 typedef struct puts_data{
 	puts_func f;
 	void *prm;
diff -urN cui105/cui_test.c cui106/cui_test.c
--- cui105/cui_test.c	2014-04-10 22:00:00.000000000 +0900
+++ cui106/cui_test.c	2014-04-11 00:00:00.000000000 +0900
@@ -418,7 +418,7 @@
 	cui small2 = cui_scpanel_sheet_get(small);

 	cui tab_sheets[] = { base, bs_bar, sc_hoge, bs_term, bs_timer, bs_num };
-
+	cui tab = cui_tab_new(bs_view, 0, 0, -1, (char *[]){"foo", "bar", "hoge", "term", "timer", "num", NULL}, tab_sheets, 0);
 	char lb_timer1_buf[16];

 	big_new(small2, 0, 0, &p);
@@ -466,7 +466,9 @@
 	cui_bind(btn_num, CUI_EVT_BUTTON, btn_num_hdr, num_arr);
 	cui_wh_exp(bs_num);

-	cui_tab_new(bs_view, 0, 0, -1, (char *[]){"foo", "bar", "hoge", "term", "timer", "num", NULL}, tab_sheets, 0);
+	cui_wh_set(bs_view, cui_x2(tab), bs_view->h);
+	cui_wh_set(bs, cui_x2(bs_view)+1, cui_y2(bs_view)+1);
+	cui_xy_set(((cui_scpanel)bs)->rszbox, bs->w-1, bs->h-1);

 	cui_main(bs, NULL);
 	printf("result %s\n", p->btn_result);
diff -urN cui105/etext.c cui106/etext.c
--- cui105/etext.c	2014-04-10 22:00:00.000000000 +0900
+++ cui106/etext.c	2014-04-11 01:00:00.000000000 +0900
@@ -117,7 +117,12 @@
 	cui_lblfix_str_set(p->lbf_shadow, p->buf);
 	p->n = strlen(p->buf);
 	cui_etext_hist_add(obj);
-	cui_etext_selall_in(obj);
+	if(p->mode == CUI_ETEXT_MODE_SELALL){
+		cui_hide(p->view_cursor);
+		p->mode = CUI_ETEXT_MODE_NORMAL;
+		cui_draw(obj);
+	}
+	else cui_etext_selall_in(obj);
 	return TRUE;
 }

@@ -149,7 +154,7 @@
 	cui obj = (cui)prm;
 	cui_etext p = (cui_etext)obj;

-	p->buf[p->cursor] = *cui_menu_popup_str_get(pm);
+	p->buf[p->cursor] = '0' + cui_menu_int_get(pm);
 	cui_lblfix_str_set(p->lbf, p->buf);
 	cui_lblfix_str_set(p->lbf_shadow, p->buf);
 	return TRUE;
@@ -159,15 +164,14 @@
 cui_etext_digit_menu(cui obj)
 {
 	cui_etext p = (cui_etext)obj;
-	char *lst[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
 	cui pm;

 	if(p->cursor >= p->n || !isdigit(p->buf[p->cursor])) return;
-	pm = cui_menu_popup_new(p->view_cursor, 0, 0, 0, lst, p->buf[p->cursor] - '0');
+	pm = cui_menu_int_new(p->view_cursor, 0, 0, 0, p->buf[p->cursor] - '0', 0, 9);
 	cui_bind(pm, CUI_EVT_BUTTON, cui_etext_digit_menu_hdr, p);
 	cui_hide(pm);
 	cui_handler_call(pm, CUI_EVT_KEY, CUI_KEY_ENTER);
-	cui_menu_popup_del_free(pm);
+	cui_menu_int_del_free(pm);
 }

 void
@@ -204,6 +208,22 @@
 	cui_show(p->view_cursor);
 }

+static void
+mode_edit_normal_key_in(cui obj, int val)
+{
+	cui_etext p = (cui_etext)obj;
+	int i;
+
+	if(p->n >= CUI_ETEXT_BSIZE - 1) return;
+	for(i=p->n; i>=p->cursor; i--) p->buf[i+1] = p->buf[i];
+	p->buf[p->cursor] = val;
+	p->n++;
+	cui_lblfix_str_set(p->lbf, p->buf);
+	cui_lblfix_str_set(p->lbf_shadow, p->buf);
+	p->cursor++;
+	cui_etext_cursor_update(obj);
+}
+
 int
 cui_etext_hdr(cui obj, int evt, int val, void *prm)
 {
@@ -242,6 +262,10 @@
 			case CUI_KEY_DOWN:
 				cui_etext_hist_menu(obj);
 				break;
+			default:
+				cui_etext_edit_in(obj, CUI_KEY_BS);
+				mode_edit_normal_key_in(obj, val);
+				break;
 			}
 			return TRUE;
 		case CUI_ETEXT_MODE_EDIT:
@@ -277,14 +301,7 @@
 				cui_etext_cursor_update(obj);
 				break;
 			default:
-				if(p->n >= CUI_ETEXT_BSIZE - 1) break;
-				for(i=p->n; i>=p->cursor; i--) p->buf[i+1] = p->buf[i];
-				p->buf[p->cursor] = val;
-				p->n++;
-				cui_lblfix_str_set(p->lbf, p->buf);
-				cui_lblfix_str_set(p->lbf_shadow, p->buf);
-				p->cursor++;
-				cui_etext_cursor_update(obj);
+				mode_edit_normal_key_in(obj, val);
 				break;
 			}
 			return TRUE;
diff -urN cui105/menu.c cui106/menu.c
--- cui105/menu.c	2014-04-10 22:00:00.000000000 +0900
+++ cui106/menu.c	2014-04-11 01:00:00.000000000 +0900
@@ -179,6 +179,7 @@
 	if(w >= 0) cui_w_set(obj, w);
 	p->menu = cui_menu_new(cui_root(obj), 0, 0, lst);
 	p->val = val;
+	p->vp = NULL;
 	p->call_btn_hdr_flag = TRUE; /* ! */

 	cui_unbind(obj, CUI_EVT_KEY | CUI_EVT_BUTTON, cui_menu_item_hdr);
@@ -209,6 +210,7 @@
 	}else if(evt == CUI_EVT_BUTTON){ /* menu */
 		cui_menu_popup p = (cui_menu_popup)prm;
 		p->val = val;
+		if(p->vp) *p->vp = p->val;
 		cui_quit();
 		return TRUE;
 	}
@@ -234,6 +236,7 @@
 	cui_menu_popup p = (cui_menu_popup)obj;
 	char *s;
 	p->val = idx;
+	if(p->vp) *p->vp = p->val;
 	s = cui_menu_item_str_get(cui_index_to_child(p->menu, p->val));
 	cui_menu_item_str_set(obj, s);
 }
@@ -246,6 +249,14 @@
 	cui_del_free(obj);
 }

+void
+cui_menu_popup_vp_set(cui obj, int *vp)
+{
+	cui_menu_popup p = (cui_menu_popup)obj;
+	p->vp = vp;
+	if(p->vp) cui_menu_popup_set(obj, *p->vp);
+}
+
 cui
 cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v)
 {
@@ -383,4 +394,11 @@
 	cui_menu_int_set(obj, p->v);
 }

+void
+cui_menu_int_del_free(cui obj)
+{
+	cui_menu_int_lst_free(obj);
+	cui_menu_popup_del_free(obj);
+}
+
 /* EOF */
diff -urN cui105/menu.h cui106/menu.h
--- cui105/menu.h	2014-04-10 22:00:00.000000000 +0900
+++ cui106/menu.h	2014-04-11 01:00:00.000000000 +0900
@@ -16,7 +16,7 @@
 typedef struct cui_menu_popup{
 	struct cui_menu_item item;
 	cui menu;
-	int val;
+	int val, *vp;
 	int call_btn_hdr_flag; /* ! */
 } *cui_menu_popup;

@@ -49,6 +49,7 @@
 int cui_menu_popup_get(cui obj);
 void cui_menu_popup_set(cui obj, int idx);
 void cui_menu_popup_del_free(cui obj);
+void cui_menu_popup_vp_set(cui obj, int *vp);

 cui cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v);
 void cui_menu_int_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v);
@@ -58,5 +59,6 @@
 void cui_menu_int_set(cui obj, int v);
 void cui_menu_int_vp_set(cui obj, int *vp);
 void cui_menu_int_min_max_set(cui obj, int min_v, int max_v);
+void cui_menu_int_del_free(cui obj);

 #endif
diff -urN cui105/num.c cui106/num.c
--- cui105/num.c	2014-04-08 22:00:00.000000000 +0900
+++ cui106/num.c	2014-04-10 23:00:00.000000000 +0900
@@ -21,6 +21,7 @@
 {
 	cui_num p = (cui_num)obj;
 	char buf[ CUI_ETEXT_BSIZE ];
+	cui eview;

 	w = w < 6 ? 6 : w;
 	cui_base_init(obj, parent, x, y, w, 1);
@@ -40,7 +41,8 @@
 	if(bar_h == 0) bar_h = w;

 	p->fb_dec = cui_fillbtn_new(obj, 0, 0, 1, 1, "<");
-	p->etx = cui_etext_new(obj, 1, 0, w-3, buf);
+	eview = cui_base_new(obj, 1, 0, w-3, 1);
+	p->etx = cui_etext_new(eview, -1, 0, w-1, buf);
 	p->fb_inc = cui_fillbtn_new(obj, cui_x2(p->etx), 0, 1, 1, ">");
 	p->fb_bar = cui_fillbtn_new(obj, cui_x2(p->fb_inc), 0, 1, 1, "|");

diff -urN cui105/tab.c cui106/tab.c
--- cui105/tab.c	2014-02-02 23:50:00.000000000 +0900
+++ cui106/tab.c	2014-04-11 00:00:00.000000000 +0900
@@ -22,9 +22,9 @@

 	p->lb1 = rd->lb1;
 	p->lb2 = rd->lb2;
-	cui_label_str_set(p->lb1, "/");
+	cui_label_str_set(p->lb1, "/ ");
 	cui_x_set(p->lb2, cui_x2(p->lb1));
-	p->lb3 = cui_label_new(obj, cui_x2(p->lb2), 0, "|");
+	p->lb3 = cui_label_new(obj, cui_x2(p->lb2), 0, " |");
 	cui_wh_fit(obj);
 	cui_bind(obj, CUI_EVT_DRAW , cui_tab_btn_hdr, NULL);

cui106_107.patch
diff -urN cui106/key.c cui107/key.c
--- cui106/key.c	Thu Mar 27 22:10:00 2014
+++ cui107/key.c	Sat Apr 12 22:00:00 2014
@@ -4,6 +4,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <sys/time.h>
 #include <termios.h>

 #define KEY_FD	0
@@ -30,11 +31,25 @@
 	tcsetattr(KEY_FD, TCSANOW, &bak);
 }

+static int
+readable_chk(int fd)
+{
+	fd_set rfs;
+	struct timeval tm;
+
+	FD_ZERO(&rfs);
+	FD_SET(fd, &rfs);
+	tm.tv_sec = 0;
+	tm.tv_usec = 100*1000;
+	return select(fd+1, &rfs, NULL, NULL, &tm) > 0;
+}
+
 int
 cui_key_get(void)
 {
 	unsigned char uc;

+	if(!readable_chk(KEY_FD)) return 0;
 	if(read(KEY_FD, &uc, 1) != 1) return 0;
 	return uc;
 }

cui107_108.patch
diff -urN cui107/Makefile cui108/Makefile
--- cui107/Makefile	Sun Apr  6 23:00:00 2014
+++ cui108/Makefile	Sat Apr 12 23:00:00 2014
@@ -7,7 +7,7 @@
 endif

 TARG = cui_test
-OBJS = cui.o focus.o num.o etext.o menu.o dialog.o timer.o term.o scpanel.o tbar.o rszbox.o scbar.o fillbtn.o tab.o radio.o ckbox.o button.o lblfix.o fill.o label.o panel.o handler.o esc.o key.o rect.o dbg.o list.o
+OBJS = cui.o select.o focus.o num.o etext.o menu.o dialog.o timer.o term.o scpanel.o tbar.o rszbox.o scbar.o fillbtn.o tab.o radio.o ckbox.o button.o lblfix.o fill.o label.o panel.o handler.o esc.o key.o rect.o dbg.o list.o

 all: $(TARG)

diff -urN cui107/cui.c cui108/cui.c
--- cui107/cui.c	Fri Apr 11 02:00:00 2014
+++ cui108/cui.c	Sat Apr 12 23:00:00 2014
@@ -1,4 +1,5 @@
 #include "cui.h"
+#include "select.h"
 #include "focus.h"
 #include "rect.h"
 #include "esc.h"
@@ -18,6 +19,84 @@
 #include <sys/time.h>
 #include <termios.h>

+/* --> for socket */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+/* <-- for socket */
+
+#define KEY_FD	0
+static int use_stdio = TRUE;
+
+static int
+arg_idx(int ac, char **av, char *targ)
+{
+	int i;
+
+	for(i=1; i<ac; i++) if(strcmp(av[i], targ) == 0) break;
+	return i ? i < ac : -1;
+}
+
+static int
+conn(char *s)
+{
+	int i, port, fd;
+	char name[1024];
+	struct sockaddr_in addr;
+	struct hostent *hp;
+
+	for(i=0; s[i]; i++) if(s[i] == ':') break;
+	if(i > 0 && s[i]){
+		memcpy(name, s, i);
+		name[i] = '\0';
+	}else strcpy(name, "localhost");
+	i = s[i] ? i + 1 : 0;
+	port = strtol(s+i, NULL, 0);
+
+	if((hp = gethostbyname(name)) == NULL) ERR("gethostbyname");
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
+	if((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) ERR("socket");
+	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) ERR("connect");
+	return fd;
+}
+
+int
+cui_init(int ac, char **av)
+{
+	int i;
+
+	cui_dbg_open(ac, av);
+	cui_select_init();
+	cui_key_init();
+	if((i = arg_idx(ac, av, "-conn")) > 0){
+		if(i+1 >= ac) ERR("-conn [name:]port");
+		cui_key_fd_add(conn(av[i+1]));
+		use_stdio = arg_idx(ac, av, "-stdio") > 0;
+	}
+	if(use_stdio){
+		cui_key_fd_add(KEY_FD);
+		cui_key_enter();
+	}
+	cui_esc_enter();
+	return 0;
+}
+
+void
+cui_fini(int init_ret)
+{
+	cui_esc_exit();
+	cui_key_fd_close(); /* without KEY_FD (0) */
+	if(use_stdio){
+		cui_key_exit();
+	}
+	cui_dbg_close();
+}
+
 typedef struct cui_main_stack *cui_main_stack;

 struct cui_main_stack{
@@ -416,50 +495,6 @@
 	cui_running_set(FALSE);
 }

-#define KEY_FD	0
-
-static fd_set sel_rfs, res_rfs;
-static int sel_inited = FALSE;
-
-void
-cui_select_init(void)
-{
-	FD_ZERO(&sel_rfs);
-	FD_SET(KEY_FD, &sel_rfs);
-	sel_inited = TRUE;
-}
-
-void
-cui_select_add(int fd)
-{
-	FD_SET(fd, &sel_rfs);
-}
-
-void
-cui_select_del(int fd)
-{
-	FD_CLR(fd, &sel_rfs);
-}
-
-int
-cui_select(void)
-{
-	int nfds = 0, fd;
-	struct timeval tm;
-
-	res_rfs = sel_rfs;
-	for(fd=0; fd<FD_SETSIZE; fd++) if(FD_ISSET(fd, &res_rfs) && fd > nfds) nfds = fd;
-	tm.tv_sec = 0;
-	tm.tv_usec = 100*1000;
-	return select(nfds+1, &res_rfs, NULL, NULL, &tm);
-}
-
-int
-cui_select_chk(int fd)
-{
-	return FD_ISSET(fd, &res_rfs);
-}
-
 typedef struct cui_readable{
 	cui obj;
 	int fd;
@@ -467,24 +502,13 @@

 static cui_list readable_list = NULL;

-static void
-cui_readable_select_setup(void)
-{
-	cui_list lp = readable_list;
-	cui_select_init();
-	for(; lp; lp=lp->next){
-		cui_readable p = (cui_readable)lp->data;
-		cui_select_add(p->fd);
-	}
-}
-
 void
 cui_readable_add(cui obj, int fd)
 {
 	cui_readable p = cui_list_add(&readable_list, sizeof(struct cui_readable));
 	p->obj = obj;
 	p->fd = fd;
-	cui_readable_select_setup();
+	cui_select_add(fd);
 }

 void
@@ -498,7 +522,7 @@
 	}
 	if(lp == NULL) return;
 	cui_list_del(&readable_list, p);
-	cui_readable_select_setup();
+	cui_select_del(fd);
 }

 void
@@ -615,8 +639,10 @@

 	if(strcmp(s, "^C") == 0) return 1;
 	else if(strcmp(s, "Quit") == 0) cui_quit();
-	else if(strcmp(s, "Redraw") == 0) cui_draw(obj);
-	else if(strcmp(s, "HTML") == 0) html(cui_root(obj));
+	else if(strcmp(s, "Redraw") == 0){
+		cui_clear(obj);
+		cui_draw(obj);
+	}else if(strcmp(s, "HTML") == 0) html(cui_root(obj));
 	/* else Cancel */
 	return 0;
 }
@@ -635,19 +661,13 @@
 	sp->prev = main_stack;
 	main_stack = sp;

-	cui_esc_enter();
-	cui_key_enter();
-
 	cui_clear(top_obj);
 	cui_draw(top_obj);

-	if(!sel_inited) cui_select_init();
-
 	while(cui_running_get()){
 		cui_timer_work();
-		if(cui_select() <= 0) continue;
+		if(cui_select(100*1000) <= 0) continue;
 		cui_readable_work();
-		if(!cui_select_chk(KEY_FD)) continue;
 		if((key = cui_key_get2()) == 0) continue;
 		if(cmenu(top_obj, key) == 0) continue;
 		focus = cui_focus_get();
@@ -661,9 +681,6 @@
 			cui_draw(obj);
 		}
 	}
-	cui_key_exit();
-	cui_esc_exit();
-
 	main_stack = sp->prev;
 }

@@ -737,8 +754,11 @@
 {
 	cui_list lp = puts_list;
 	if(puts_list == NULL){
-		printf("%s", s);
-		fflush(stdout);
+		int fd, n = strlen(s);
+		for(fd=0; fd<FD_SETSIZE; fd++){
+			if(!cui_key_fd_chk(fd)) continue;
+			write(fd, s, n);
+		}
 		return;
 	}
 	for(; lp; lp=lp->next){
diff -urN cui107/cui.h cui108/cui.h
--- cui107/cui.h	Thu Apr 10 22:00:00 2014
+++ cui108/cui.h	Sat Apr 12 23:00:00 2014
@@ -43,6 +43,9 @@

 extern cui_rect cui_clip;

+int cui_init(int ac, char **av);
+void cui_fini(int init_ret);
+
 cui cui_alloc(int size);
 void cui_base_init(cui obj, cui parent, int x, int y, int w, int h);
 void cui_base_child_add(cui obj, cui child);
diff -urN cui107/cui_test.c cui108/cui_test.c
--- cui107/cui_test.c	Fri Apr 11 00:00:00 2014
+++ cui108/cui_test.c	Sat Apr 12 23:00:00 2014
@@ -381,7 +381,7 @@
 int
 main(int ac, char **av)
 {
-	int dbg_ret = cui_dbg_open(ac, av);
+	int init_ret = cui_init(ac, av);
 	cui bs = cui_scpanel_new(NULL, 0, 0, 42, 23, "cui_test");
 	cui bs_view = ((cui_scpanel)bs)->view;
 	cui bs_bar = cui_base_new(bs_view, 0, 1, 0, 0);
@@ -473,7 +473,7 @@
 	cui_main(bs, NULL);
 	printf("result %s\n", p->btn_result);
 	cui_free(bs);
-	if(dbg_ret == 0) cui_dbg_close();
+	cui_fini(init_ret);
 	return 0;
 }

diff -urN cui107/dbg.c cui108/dbg.c
--- cui107/dbg.c	Tue Apr  1 22:00:00 2014
+++ cui108/dbg.c	Sat Apr 12 23:00:00 2014
@@ -7,25 +7,24 @@
 static FILE *fp = NULL;
 int cui_dbg_enable = 1;

-int
+void
 cui_dbg_open(int ac, char **av)
 {
 	char buf[1024];
 	int i;
 	char *nc_opt = "localhost 12345";

-	if(fp) return 0;
+	if(fp) return;

 	for(i=1; i<ac; i++) if(strcmp(av[i], "-dbg-en0") == 0) break;
 	if(i<ac) cui_dbg_enable = 0;

 	for(i=1; i<ac; i++) if(strcmp(av[i], "-dbg") == 0) break;
-	if(i >= ac) return 0;
+	if(i >= ac) return;

 	if(i+1 < ac) nc_opt = av[i+1];
 	sprintf(buf, "nc %s", nc_opt);
-	if((fp = popen(buf, "w")) == NULL) return -1;
-	return 0;
+	if((fp = popen(buf, "w")) == NULL) perror("popen");
 }

 void
diff -urN cui107/dbg.h cui108/dbg.h
--- cui107/dbg.h	Mon Mar 31 01:00:00 2014
+++ cui108/dbg.h	Sat Apr 12 23:00:00 2014
@@ -5,7 +5,7 @@

 extern int cui_dbg_enable;

-int cui_dbg_open(int ac, char **av);
+void cui_dbg_open(int ac, char **av);
 void cui_dbg_close(void);
 void cui_dbg(char *fmt, ...);

diff -urN cui107/key.c cui108/key.c
--- cui107/key.c	Sat Apr 12 22:00:00 2014
+++ cui108/key.c	Sat Apr 12 23:00:00 2014
@@ -1,5 +1,5 @@
 #include "key.h"
-
+#include "select.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -9,15 +9,20 @@

 #define KEY_FD	0

-int cui_key_cnt = 0;
 static struct termios bak;
+fd_set cui_key_fds;
+
+void
+cui_key_init(void)
+{
+	FD_ZERO(&cui_key_fds);
+}

 void
 cui_key_enter(void)
 {
 	struct termios raw;

-	if(cui_key_cnt++ > 0) return;
 	tcgetattr(KEY_FD, &bak);
 	cfmakeraw(&raw);
 	raw.c_lflag &= ~FLUSHO;
@@ -27,21 +32,27 @@
 void
 cui_key_exit(void)
 {
-	if(--cui_key_cnt > 0) return;
 	tcsetattr(KEY_FD, TCSANOW, &bak);
 }

-static int
-readable_chk(int fd)
+void
+cui_key_fd_add(int fd)
 {
-	fd_set rfs;
-	struct timeval tm;
+	FD_SET(fd, &cui_key_fds);
+	cui_select_add(fd);
+}
+
+void
+cui_key_fd_del(int fd)
+{
+	FD_CLR(fd, &cui_key_fds);
+	cui_select_del(fd);
+}

-	FD_ZERO(&rfs);
-	FD_SET(fd, &rfs);
-	tm.tv_sec = 0;
-	tm.tv_usec = 100*1000;
-	return select(fd+1, &rfs, NULL, NULL, &tm) > 0;
+int
+cui_key_fd_chk(int fd)
+{
+	return FD_ISSET(fd, &cui_key_fds);
 }

 int
@@ -49,22 +60,50 @@
 {
 	unsigned char uc;

-	if(!readable_chk(KEY_FD)) return 0;
+	if(!cui_select_readable_chk(KEY_FD, 100*1000)) return 0;
 	if(read(KEY_FD, &uc, 1) != 1) return 0;
 	return uc;
 }

+void
+cui_key_fd_close(void) /* without KEY_FD (0) */
+{
+	int fd;
+	for(fd=1; fd<FD_SETSIZE; fd++){
+		if(!cui_key_fd_chk(fd)) continue;
+		cui_key_fd_del(fd);
+		close(fd);
+	}
+}
+
+static int
+key_get(int fd)
+{
+	unsigned char uc;
+
+	if(!cui_select_readable_chk(fd, 100*1000)) return 0;
+	if(read(fd, &uc, 1) != 1) return 0;
+	return uc;
+}
+
 int
 cui_key_get2(void)
 {
+	int fd;
 	int k, k2, k3;

-	k= cui_key_get();
+	for(fd=0; fd<FD_SETSIZE; fd++){
+		if(!cui_key_fd_chk(fd)) continue;
+		if(cui_select_readable_chk(fd, 0)) break;
+	}
+	if(fd >= FD_SETSIZE) return 0;
+
+	k = key_get(fd);
 	if(k == 0x7f) return CUI_KEY_BS;
 	if(k == CUI_KEY_ESC){
-		k2 = cui_key_get();
+		k2 = key_get(fd);
 		if(k2 == '['){
-			k3 = cui_key_get();
+			k3 = key_get(fd);
 			switch(k3){
 			case 'A': return CUI_KEY_UP;
 			case 'B': return CUI_KEY_DOWN;
diff -urN cui107/key.h cui108/key.h
--- cui107/key.h	Thu Apr  3 23:00:00 2014
+++ cui108/key.h	Sat Apr 12 23:00:00 2014
@@ -1,6 +1,9 @@
 #ifndef __KEY_H__
 #define __KEY_H__

+#include <sys/types.h> /* for fd_set */
+#include <sys/select.h>
+
 #define CUI_KEY_ENTER	0x0d /* '\r' */
 #define CUI_KEY_BS	0x08 /* '\b' */
 #define CUI_KEY_CTRL(c)	(0x01+(c)-'A')
@@ -10,8 +13,16 @@
 #define CUI_KEY_RIGHT	CUI_KEY_CTRL('F')
 #define CUI_KEY_ESC	0x1b

+void cui_key_init(void);
 void cui_key_enter(void);
 void cui_key_exit(void);
+
+extern fd_set cui_key_fds;
+void cui_key_fd_add(int fd);
+void cui_key_fd_del(int fd);
+int cui_key_fd_chk(int fd);
+void cui_key_fd_close(void); /* without KEY_FD (0) */
+
 int cui_key_get(void);
 int cui_key_get2(void);

diff -urN cui107/select.c cui108/select.c
--- cui107/select.c	Thu Jan  1 09:00:00 1970
+++ cui108/select.c	Sat Apr 12 23:00:00 2014
@@ -0,0 +1,60 @@
+#include "select.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+static fd_set sel_rfs, res_rfs;
+
+void
+cui_select_init(void)
+{
+	FD_ZERO(&sel_rfs);
+}
+
+void
+cui_select_add(int fd)
+{
+	FD_SET(fd, &sel_rfs);
+}
+
+void
+cui_select_del(int fd)
+{
+	FD_CLR(fd, &sel_rfs);
+}
+
+int
+cui_select(int usec)
+{
+	int nfds = 0, fd;
+	struct timeval tm;
+
+	res_rfs = sel_rfs;
+	for(fd=0; fd<FD_SETSIZE; fd++) if(FD_ISSET(fd, &res_rfs) && fd > nfds) nfds = fd;
+	tm.tv_sec = 0;
+	tm.tv_usec = usec;
+	return select(nfds+1, &res_rfs, NULL, NULL, &tm);
+}
+
+int
+cui_select_chk(int fd)
+{
+	return FD_ISSET(fd, &res_rfs);
+}
+
+int
+cui_select_readable_chk(int fd, int usec)
+{
+	fd_set rfs;
+	struct timeval tm;
+
+	FD_ZERO(&rfs);
+	FD_SET(fd, &rfs);
+	tm.tv_sec = 0;
+	tm.tv_usec = usec;
+	return select(fd+1, &rfs, NULL, NULL, &tm) > 0;
+}
+
+/* EOF */
diff -urN cui107/select.h cui108/select.h
--- cui107/select.h	Thu Jan  1 09:00:00 1970
+++ cui108/select.h	Sat Apr 12 23:00:00 2014
@@ -0,0 +1,12 @@
+#ifndef __SELECT_H__
+#define __SELECT_H__
+
+void cui_select_init(void);
+void cui_select_add(int fd);
+void cui_select_del(int fd);
+int cui_select(int usec);
+int cui_select_chk(int fd);
+
+int cui_select_readable_chk(int fd, int usec);
+
+#endif
diff -urN cui107/term.c cui108/term.c
--- cui107/term.c	Thu Apr  3 23:00:00 2014
+++ cui108/term.c	Sat Apr 12 23:00:00 2014
@@ -477,6 +477,7 @@
 	dup2(slave, 1);
 	dup2(slave, 2);
 #endif
+	cui_key_fd_close();
 	cui_dbg_close();
 	execl("/bin/sh", "/bin/sh", "-i", (char *)NULL);
 	exit(0);

cui108_109.patch
diff -urN cui108/cui.c cui109/cui.c
--- cui108/cui.c	Sat Apr 12 23:00:00 2014
+++ cui109/cui.c	Sun Apr 13 22:00:00 2014
@@ -65,6 +65,54 @@
 	return fd;
 }

+static int sfd = -1;
+
+static void
+srv(char *s)
+{
+	int port = strtol(s, NULL, 0);
+	struct sockaddr_in saddr;
+
+	if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) ERR("socket");
+	memset(&saddr, 0, sizeof(saddr));
+	saddr.sin_family = AF_INET;
+	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
+	saddr.sin_port = htons(port);
+	if(bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) ERR("bind");
+	if(listen(sfd, 5) == -1) ERR("listen");
+}
+
+static void
+srv_accept(cui obj)
+{
+	struct sockaddr_in caddr;
+	int clen = sizeof(caddr), cfd;
+
+	if(sfd == -1 || !cui_select_chk(sfd)) return;
+
+	memset(&caddr, 0, clen);
+	if((cfd = accept(sfd, (struct sockaddr *)&caddr, &clen)) == -1) ERR("accept");
+
+	cui_key_fd_add(cfd);
+	cui_puts_temp_fd_enter(cfd);
+	cui_esc_enter();
+	cui_clear(obj);
+	cui_draw(obj);
+	cui_puts_temp_fd_exit();
+}
+
+static void
+disconn(void)
+{
+	int fd = cui_key_last_fd;
+	if(fd == 0) return;
+	cui_puts_temp_fd_enter(fd);
+	cui_esc_exit();
+	cui_puts_temp_fd_exit();
+	cui_key_fd_del(fd);
+	close(fd);
+}
+
 int
 cui_init(int ac, char **av)
 {
@@ -78,6 +126,12 @@
 		cui_key_fd_add(conn(av[i+1]));
 		use_stdio = arg_idx(ac, av, "-stdio") > 0;
 	}
+	if((i = arg_idx(ac, av, "-srv")) > 0){
+		if(i+1 >= ac) ERR("-srv port");
+		srv(av[i+1]);
+		cui_select_add(sfd);
+		use_stdio = arg_idx(ac, av, "-stdio") > 0;
+	}
 	if(use_stdio){
 		cui_key_fd_add(KEY_FD);
 		cui_key_enter();
@@ -627,7 +681,7 @@
 cmenu(cui obj, int key)
 {
 	cui mn;
-	char *lst[] = { "Cancel", "^C", "Redraw", "HTML", "Quit", NULL }, *s;
+	char *lst[] = { "Cancel", "^C", "Redraw", "Disconn", "HTML", "Quit", NULL }, *s;

 	if(key != CUI_KEY_CTRL('C')) return key;

@@ -643,6 +697,7 @@
 		cui_clear(obj);
 		cui_draw(obj);
 	}else if(strcmp(s, "HTML") == 0) html(cui_root(obj));
+	else if(strcmp(s, "Disconn") == 0) disconn();
 	/* else Cancel */
 	return 0;
 }
@@ -668,6 +723,7 @@
 		cui_timer_work();
 		if(cui_select(100*1000) <= 0) continue;
 		cui_readable_work();
+		srv_accept(top_obj);
 		if((key = cui_key_get2()) == 0) continue;
 		if(cmenu(top_obj, key) == 0) continue;
 		focus = cui_focus_get();
@@ -747,6 +803,27 @@
 	for(; lp; lp=lp->next) if(*(puts_func *)lp->data == f) break;
 	if(lp == NULL) return;
 	cui_list_del(&puts_list, lp->data);
+}
+
+static void
+temp_puts(char *s, void *prm)
+{
+	int fd = *(int *)prm, n = strlen(s);
+	write(fd, s, n);
+}
+
+void
+cui_puts_temp_fd_enter(int fd)
+{
+	static int tmp_fd;
+	tmp_fd = fd;
+	cui_puts_add(temp_puts, &tmp_fd);
+}
+
+void
+cui_puts_temp_fd_exit(void)
+{
+	cui_puts_del(temp_puts);
 }

 void
diff -urN cui108/cui.h cui109/cui.h
--- cui108/cui.h	Sat Apr 12 23:00:00 2014
+++ cui109/cui.h	Sun Apr 13 22:00:00 2014
@@ -108,6 +108,8 @@
 typedef void (*puts_func)(char *, void *prm);
 void cui_puts_add(puts_func f, void *prm);
 void cui_puts_del(puts_func f);
+void cui_puts_temp_fd_enter(int fd);
+void cui_puts_temp_fd_exit(void);
 void cui_puts(char *s);

 #endif
diff -urN cui108/key.c cui109/key.c
--- cui108/key.c	Sat Apr 12 23:00:00 2014
+++ cui109/key.c	Sun Apr 13 22:00:00 2014
@@ -76,6 +76,8 @@
 	}
 }

+int cui_key_last_fd = -1;
+
 static int
 key_get(int fd)
 {
@@ -83,6 +85,7 @@

 	if(!cui_select_readable_chk(fd, 100*1000)) return 0;
 	if(read(fd, &uc, 1) != 1) return 0;
+	cui_key_last_fd = fd;
 	return uc;
 }

diff -urN cui108/key.h cui109/key.h
--- cui108/key.h	Sat Apr 12 23:00:00 2014
+++ cui109/key.h	Sun Apr 13 22:00:00 2014
@@ -26,4 +26,6 @@
 int cui_key_get(void);
 int cui_key_get2(void);

+extern int cui_key_last_fd;
+
 #endif

$ (cd cui ; make clean)
$ cat cui104_105.patch cui105_106.patch cui106_107.patch cui107_108.patch cui108_109.patch | (cd cui ; patch -p1)
$ (cd cui ; make lib)

CUI側で初期化処理などのAPIを変更したので、cui_midi.c を対応させます。

終了処理も、^Cメニューからの Quit処理を追加したので、(Quit)ボタンは削除します

cui_midi.c の差分です

cui_midi5.patch
--- cui_midi.c-	Wed Apr  9 01:00:00 2014
+++ cui_midi.c	Sun Apr 13 23:00:00 2014
@@ -105,10 +105,6 @@
 	cui lb = prm_arr[1];
 	char *lb_buf = cui_label_str_get(lb);

-	if(strcmp(s, "Quit") == 0){
-		cui_quit();
-		return TRUE;
-	}
 	if(strcmp(s, "Add") == 0){
 		int v = hex_in_get(hxi);
 		set_char(v);
@@ -238,10 +234,10 @@
 int
 main(int ac, char **av)
 {
-	int dbg_ret = cui_dbg_open(ac, av);
+	int init_ret = cui_init(ac, av);
 	cui root = cui_scpanel_new(NULL, 0, 0, 30, 10, "cui_midi");
 	cui_scpanel scp = (cui_scpanel)root;
-	cui quit = cui_button_new(scp->view, 2, 1, "Quit");
+	cui quit = cui_base_new(scp->view, 2, 1, 0, 1); /* dmy */

 	cui bs1 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
 	cui hxi = hex_in_new(bs1, 0, 0, 0);
@@ -274,7 +270,6 @@

 	cui_wh_fit(bs1);

-	cui_bind(quit, CUI_EVT_BUTTON, btn_hdr, prm_arr);
 	cui_bind(add, CUI_EVT_BUTTON, btn_hdr, prm_arr);
 	cui_bind(out, CUI_EVT_BUTTON, btn_hdr, prm_arr);

@@ -359,8 +354,8 @@
 	/**/

 	cui_show(bs3);
-	cui_tab_new(scp->view, cui_x2(quit)+2, quit->y, -1,
-		    (char *[]){"1st ", "2nd ", "3rd ", NULL},
+	cui_tab_new(scp->view, quit->x, quit->y, -1,
+		    (char *[]){"1st", "2nd", "3rd", NULL},
 		    tab_sheets, 2);

 	cui_wh_fit(scp->view);
@@ -387,7 +382,7 @@
 	cui_main(root, NULL);

 	pclose(fp);
-	if(dbg_ret == 0) cui_dbg_close();
+	cui_fini(init_ret);
 	return 0;
 }

cui_midi.c を更新してビルドし、実行します

$ patch -p0 < cui_midi5.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui
$ ./cui_midi
========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |                                    |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <0    >|                                    |
|                                                           |
|    vol (msb) : <0    >|                                   |
|                                                           |
|    note : on    |  num : <69   >|  velo : <64   >|  (Out) |
|                                                           |
|                                                           |
+-----------------------------------------------------------+

多少すっきりしました

以前と同じように操作できて、終了は ^C から Quit を選択します

それでは本題の「楽器の」鍵盤の部品の実装です

cui_midi.c の差分です

cui_midi6.patch
--- cui_midi.c-	Sun Apr 13 23:00:00 2014
+++ cui_midi.c	Sun Apr 13 23:30:00 2014
@@ -231,6 +231,249 @@
 	return TRUE;
 }

+/**/
+
+typedef struct cui_kbd{
+	struct cui_base base;
+	int v[128];
+	int play_mode;
+	int cursor;
+	cui sheet;
+} *cui_kbd;
+
+cui cui_kbd_new(cui parent, int x, int y, int w);
+void cui_kbd_init(cui obj, cui parent, int x, int y, int w);
+int cui_kbd_hdr(cui obj, int evt, int val, void *prm);
+void cui_kbd_set(cui obj, int note, int v);
+int cui_kbd_get(cui obj, int note);
+
+
+#include "cui/key.h"
+
+static int
+kbd_note_x(int note)
+{
+	int i = note % 12;
+	int o = note / 12;
+	return o * 14 + i + (i >= 5);
+}
+
+static int
+kbd_x_note(int x)
+{
+	int o = x / 14;
+	int i = x % 14;
+	if(i == 5 || i == 13) return -1;
+	return o * 12 + i - (i >= 5);
+}
+
+cui
+cui_kbd_new(cui parent, int x, int y, int w)
+{
+	cui obj = cui_alloc(sizeof(struct cui_kbd));
+	cui_kbd_init(obj, parent, x, y, w);
+	return obj;
+}
+
+void
+cui_kbd_init(cui obj, cui parent, int x, int y, int w)
+{
+	cui_kbd p = (cui_kbd)obj;
+	int i;
+
+	cui_base_init(obj, parent, x, y, w, 2);
+	p->sheet = cui_base_new(obj, 0, 0, kbd_note_x(128), 2);
+
+	obj->flags |= CUI_FLG_CAN_FOCUS;
+	for(i=0; i<128; i++) p->v[i] = FALSE;
+	p->play_mode = FALSE;
+	p->cursor = 69;
+	cui_x_set(p->sheet, -kbd_note_x(p->cursor));
+
+	cui_bind(obj, CUI_EVT_DRAW | CUI_EVT_KEY, cui_kbd_hdr, NULL);
+}
+
+static void
+kbd_draw_x_char_attr(cui obj, int fc, int x, char *c_u, int *a_u, char *c_l, int *a_l)
+{
+	cui_kbd p = (cui_kbd)obj;
+	int note = kbd_x_note(x);
+	int blk = x & 1;
+	*c_u = note < 0 ? '|' : ' ';
+	*a_u = note >= 0 && blk ? CUI_ATTR_REVERSE : CUI_ATTR_NORMAL;
+	*c_l = blk ? '|' : ' ';
+	*a_l = CUI_ATTR_NORMAL;
+
+	if(p->play_mode) *a_l |= CUI_ATTR_ULINE;
+	if(note == p->cursor){
+		if(blk) *c_l = '^';
+		else *a_u |= CUI_ATTR_ULINE;
+	}
+	if(note >= 0 && p->v[note]){
+		if(blk) *c_u = 'o';
+		else *c_l = 'o';
+	}
+	if(!p->play_mode && fc){
+		*a_u ^= CUI_ATTR_REVERSE;
+		*a_l ^= CUI_ATTR_REVERSE;
+	}
+}
+
+static void
+kbd_draw_x(cui obj, int fc, int x)
+{
+	cui_kbd p = (cui_kbd)obj;
+	char c_u, c_l;
+	int a_u, a_l;
+	char buf[2];
+	buf[1] = '\0';
+
+	kbd_draw_x_char_attr(obj, fc, x, &c_u, &a_u, &c_l, &a_l);
+
+	buf[0] = c_u;
+	cui_draw_str(p->sheet, x, 0, buf, a_u);
+
+	buf[0] = c_l;
+	cui_draw_str(p->sheet, x, 1, buf, a_l);
+}
+
+static void
+kbd_draw_note(cui obj, int fc, int note)
+{
+	kbd_draw_x(obj, fc, kbd_note_x(note));
+}
+
+static void
+kbd_draw(cui obj, int fc)
+{
+	cui_kbd p = (cui_kbd)obj;
+	int x;
+	for(x=0; x<p->sheet->w; x++) kbd_draw_x(obj, fc, x);
+}
+
+int
+cui_kbd_hdr(cui obj, int evt, int val, void *prm)
+{
+	cui_kbd p = (cui_kbd)obj;
+
+	switch(evt){
+	case CUI_EVT_DRAW:
+		kbd_draw(obj, val == CUI_DRAW_FOCUS);
+		break;
+	case CUI_EVT_KEY:
+		if(!p->play_mode){
+			if(val == CUI_KEY_ENTER){
+				p->play_mode = TRUE;
+				cui_draw(obj);
+				return TRUE;
+			}
+			return FALSE;
+		}
+		/* play_mode */
+		switch(val){
+		case CUI_KEY_ENTER:
+			p->play_mode = FALSE;
+			cui_draw(obj);
+			break;
+		case CUI_KEY_LEFT:
+			if(p->cursor > 0){
+				int mv = -( p->sheet->x + kbd_note_x(p->cursor - 1) );
+				if(mv > 0){
+					cui_x_set(p->sheet, p->sheet->x + mv);
+					cui_draw(obj);
+				}
+				kbd_draw_note(obj, FALSE, p->cursor--);
+				kbd_draw_note(obj, FALSE, p->cursor);
+			}
+			break;
+		case CUI_KEY_RIGHT:
+			if(p->cursor < 127){
+				int mv = p->sheet->x + kbd_note_x(p->cursor + 1) - (obj->w - 1);
+				if(mv > 0){
+					cui_x_set(p->sheet, p->sheet->x - mv);
+					cui_draw(obj);
+				}
+				kbd_draw_note(obj, FALSE, p->cursor++);
+				kbd_draw_note(obj, FALSE, p->cursor);
+			}
+			break;
+		case CUI_KEY_UP:
+			if(p->v[ p->cursor ]){
+				p->v[ p->cursor ] = FALSE;
+				kbd_draw_note(obj, FALSE, p->cursor);
+				cui_handler_call(obj, CUI_EVT_BUTTON, p->cursor);
+			}
+			break;
+		case CUI_KEY_DOWN:
+			if(!p->v[ p->cursor ]){
+				p->v[ p->cursor ] = TRUE;
+				kbd_draw_note(obj, FALSE, p->cursor);
+				cui_handler_call(obj, CUI_EVT_BUTTON, p->cursor | 0x100); /* ON flag */
+			}
+			break;
+		}
+		return TRUE;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+void
+cui_kbd_set(cui obj, int note, int v)
+{
+	cui_kbd p = (cui_kbd)obj;
+	p->v[ note ] = v;
+	kbd_draw_note(obj, cui_focus_get() == obj, note);
+}
+
+int
+cui_kbd_get(cui obj, int note)
+{
+	cui_kbd p = (cui_kbd)obj;
+	return p->v[ note ];
+}
+
+/**/
+
+struct test4_ch{
+	int prog, vol, velo;
+	cui kbd;
+};
+
+struct test4{
+	cui mn_ch, prog, vol, velo;
+	int midi_ch, bak_ch;
+	struct test4_ch ch[16];
+};
+
+int
+t4_btn_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	struct test4 *p = (struct test4 *)prm;
+	struct test4_ch *ch = &p->ch[ p->midi_ch ];
+
+	if(obj == p->mn_ch){
+		cui_num_vp_set(p->prog, &ch->prog);
+		cui_num_vp_set(p->vol, &ch->vol);
+		cui_num_vp_set(p->velo, &ch->velo);
+
+		cui_hide(p->ch[ p->bak_ch ].kbd);
+		cui_show(ch->kbd);
+		p->bak_ch = p->midi_ch;
+	}else if(obj == p->prog){
+		set_char2_wrt((0xc<<4) | p->midi_ch, ch->prog);
+	}else if(obj == p->vol){
+		set_char3_wrt((0xb<<4) | p->midi_ch, 7 /* vol msb */, ch->vol);
+	}else if(obj == ch->kbd){
+		if(val & 0x100) set_char3_wrt((9<<4) | p->midi_ch, val & ~0x100, ch->velo);
+		else set_char3_wrt((8<<4) | p->midi_ch, val & ~0x100, ch->velo);
+	}else return FALSE;
+	return TRUE;
+}
+
 int
 main(int ac, char **av)
 {
@@ -254,16 +497,21 @@

 	cui bs3 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
 	struct test3 t3;
+
+	cui bs4 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
+	struct test4 t4;
+
 	cui obj;

 	char *cmd = "./add_delta | ./prog31 -V0 -q -play";
 	int div = 96;
 	int i;

-	cui tab_sheets[] = { bs1, bs2, bs3 };
+	cui tab_sheets[] = { bs1, bs2, bs3, bs4 };
 	cui_hide(bs1);
 	cui_hide(bs2);
 	cui_hide(bs3);
+	cui_hide(bs4);

 	lb_buf[0] = '\0';
 	cui_label_str_set(lb, lb_buf);
@@ -351,12 +599,44 @@

 	cui_handler_call(t3.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t3.mn_ch));

+	/* t4 */
+
+	t4.midi_ch = t4.bak_ch = 0;
+	for(i=0; i<16; i++){
+		t4.ch[i].prog = 0;
+		t4.ch[i].vol = 0;
+		t4.ch[i].velo = 64;
+		t4.ch[i].kbd = cui_kbd_new(bs4, 0, 8, 50);
+		cui_hide(t4.ch[i].kbd);
+		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	}
+	obj = cui_label_new(bs4, 0, 0, "midi ch : ");
+	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), 0, 4, 0, 0, 15);
+	cui_menu_int_vp_set(t4.mn_ch, &t4.midi_ch);
+
+	obj = cui_label_new(bs4, 2, 2, "prog num : ");
+	t4.prog = cui_num_new(bs4, cui_x2(obj), 2, 8, 0, 0, 127, 0);
+
+	obj = cui_label_new(bs4, 2, 4, "vol (msb) : ");
+	t4.vol = cui_num_new(bs4, cui_x2(obj), 4, 8, 0, 0, 127, 0);
+
+	obj = cui_label_new(bs4, 2, 6, "velo : ");
+	t4.velo = cui_num_new(bs4, cui_x2(obj), 6, 8, 0, 0, 127, 0);
+
+	cui_wh_fit(bs4);
+
+	cui_bind(t4.mn_ch, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.prog, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.vol, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+
+	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));
+
 	/**/

-	cui_show(bs3);
+	cui_show(bs4);
 	cui_tab_new(scp->view, quit->x, quit->y, -1,
-		    (char *[]){"1st", "2nd", "3rd", NULL},
-		    tab_sheets, 2);
+		    (char *[]){"1st", "2nd", "3rd", "4th", NULL},
+		    tab_sheets, 3);

 	cui_wh_fit(scp->view);
 	cui_wh_exp(bs1);

cui_midi.c を更新してビルドし、実行します

$ patch -p0 < cui_midi6.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui
$ ./cui_midi
========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |/ 4th |                             |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <0    >|                                    |
|                                                           |
|    vol (msb) : <0    >|                                   |
|                                                           |
|    velo : <64   >|                                        |
|                                                           |
|     |     |       |     |       |     |       |           |
|   | | | | | | | | | | | | | | | | | | | | | | | | |       |
|                                                           |
+-----------------------------------------------------------+

よしよし

まず

prog num を 50 に設定
vol (msb) を 64 に設定

矢印キーで鍵盤全体にフォーカスすると、反転表示になります

========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |/ 4th |                             |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <50   >|                                    |
|                                                           |
|    vol (msb) : <64   >|                                   |
|                                                           |
|    velo : <64   >|                                        |
|                                                           |
|     |     |       |     |       |     |       |           |
|   | | | | | | | | | | | | | | | | | | | | | | | | |       |
|                                                           |
+-----------------------------------------------------------+

ENTERキーで、鍵盤を操作するモードに入ります

========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |/ 4th |                             |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <50   >|                                    |
|                                                           |
|    vol (msb) : <64   >|                                   |
|                                                           |
|    velo : <64   >|                                        |
|                                                           |
|     |     |       |     |       |     |       |           |
|   | | | | | | | | | | | | | | | | | | | | | | | | |       |
|                                                           |
+-----------------------------------------------------------+

お判りでしょうか?

このモードに入ると、下側にライン表示が追加されます

この下のラインの有無が、通常の状態と、鍵盤操作モードに入ってる状態との違いです

左端の短いラインが、鍵盤上のカーソルです

左右の矢印キーで移動します

適当に移動して、下矢印キーで、鍵盤を押した状態になって、音が鳴ります

上矢印キーで、鍵盤を上げた状態になって、音が止まります

========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |/ 4th |                             |
|                                                           |
|  midi ch : 0  |                                           |
|                                                           |
|    prog num : <50   >|                                    |
|                                                           |
|    vol (msb) : <64   >|                                   |
|                                                           |
|    velo : <64   >|                                        |
|                                                           |
|     |     |       |     |       |     |       |           |
|   | | | | | | |o| | | | | | | | | | | | | | | | | |       |
|                                                           |
+-----------------------------------------------------------+

カーソルが端までくると、スクロールするので、note番号 0 から 127 まで指定できます

ですが、鍵盤が多すぎて今の位置がよく判りません (>_<)

初期値は、note番号69 の 440Hz の「ラ」になってます

改良の余地ありですね

ENTERキー で鍵盤操作モードを抜けて、 反転表示のフォーカスモードに戻ります

この鍵盤の部品は、チャンネルごとに保持してるので、 midi ch を切替えると、そのチャンネルの最後の状態が表示されます

midi ch を 9 にして、例によってドラムの音を試してみます

midi ch を 9 に設定
vol (msb) を 64 に設定
鍵盤にフォーカスして ENTER

これで note番号 69 にカーソルがあるはずなので

69-37 = 32

32回の左矢印キー連打です

========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |/ 4th |                             |
|                                                           |
|  midi ch : 9  |                                           |
|                                                           |
|    prog num : <0    >|                                    |
|                                                           |
|    vol (msb) : <64   >|                                   |
|                                                           |
|    velo : <64   >|                                        |
|                                                           |
|      |       |     |       |     |       |     |          |
|  ^ | | | | | | | | | | | | | | | | | | | | | | | |        |
|                                                           |
+-----------------------------------------------------------+

note番号37は黒鍵だったんですね

これまで意識してなかったです

下矢印キーで

========================= cui_midi ==========================
|                                                           |
|  / 1st |/ 2nd |/ 3rd |/ 4th |                             |
|                                                           |
|  midi ch : 9  |                                           |
|                                                           |
|    prog num : <0    >|                                    |
|                                                           |
|    vol (msb) : <64   >|                                   |
|                                                           |
|    velo : <64   >|                                        |
|                                                           |
|  o   |       |     |       |     |       |     |          |
|  ^ | | | | | | | | | | | | | | | | | | | | | | | |        |
|                                                           |
+-----------------------------------------------------------+

「カツーン」と鳴りました

OKです (^_^v

CUIから音出ししてみる (その5)

CUI14 の方を更新したので、反映しておきます。

次のパッチファイル を引っ張ってきます。

cui_update1/ というディレクトリを作り、 上記のファイルを入れておいて、ライブラリを更新します。

$ rm -f cui_update1
$ mkdir cui_update1
$ mv cui_109_110.patch ...上記のファイル... cui_update1/

$ (cd cui ; make clean)
$ cat cui_update1/* | (cd cui ; patch -p1)
$ (cd cui ; make)

CUI14 側の APIを変更した箇所があるので、 cui_midi.c の機能はそのままで、変更に対応させておきます。

CUI14 を更新したので ネットワーク対応 により、キーと表示の入出力は標準入出力を使わずに、 TCPソケットが使えるようになりました。

ここまで cui_midi からは、popen でコマンドを起動して、 データ出力を繋いでましたが、キーと表示はソケットにして、 標準出力はデータ用として使います。

cui_midi.c の差分です

cui_midi7.patch
--- cui_midi.c-	Sun Apr 13 23:30:00 2014
+++ cui_midi.c	Sat Apr 19 22:00:00 2014
@@ -477,7 +477,7 @@
 int
 main(int ac, char **av)
 {
-	int init_ret = cui_init(ac, av);
+	int init_ret = cui_init(av);
 	cui root = cui_scpanel_new(NULL, 0, 0, 30, 10, "cui_midi");
 	cui_scpanel scp = (cui_scpanel)root;
 	cui quit = cui_base_new(scp->view, 2, 1, 0, 1); /* dmy */
@@ -503,7 +503,6 @@

 	cui obj;

-	char *cmd = "./add_delta | ./prog31 -V0 -q -play";
 	int div = 96;
 	int i;

@@ -633,19 +632,17 @@

 	/**/

-	cui_show(bs4);
+	cui_wh_fit(scp->view);
+
 	cui_tab_new(scp->view, quit->x, quit->y, -1,
 		    (char *[]){"1st", "2nd", "3rd", "4th", NULL},
 		    tab_sheets, 3);

-	cui_wh_fit(scp->view);
 	cui_wh_exp(bs1);
 	cui_wh_set(root, scp->view->w+3, scp->view->h+3);
 	cui_xy_set(scp->rszbox, root->w-1, root->h-1);

-	for(i=1; i<ac; i++) if(strcmp(av[i], "-cmd") == 0) break;
-	if(i+1 < ac) cmd = av[i+1];
-	if((fp = popen(cmd, "w")) == NULL) ERR("popen");
+	fp = stdout;

 	for(i=1; i<ac; i++) if(strcmp(av[i], "-div") == 0) break;
 	if(i+1 < ac) div = strtol(av[i+1], NULL, 0);
@@ -661,7 +658,6 @@

 	cui_main(root, NULL);

-	pclose(fp);
 	cui_fini(init_ret);
 	return 0;
 }

$ patch -p0 < cui_midi7.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui

別の端末で、ポート 9070 で cui_srv を起動します.

$ cui/cui_srv -port 9070

リサイズボックスで +5 程、幅を広げておきます

============================ cui_srv ============================
|(conn)                                                         |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
|                                                               |
+---------------------------------------------------------------+

cui_midi を起動して、ポート 9070 の cui_srv に繋ぎます。

$ ./cui_midi -conn 9070 | ./add_delta | ./prog31 -V0 -q -play

別の端末の cui_srv に画面が表示されます。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <0    >|                                    |  |
||                                                           |  |
||    vol (msb) : <0    >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||     |     |       |     |       |     |       |           |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
|                                                               |
+---------------------------------------------------------------+

矢印キーで cui_midi の termin にフォーカスします

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|///////////////////////////////////////////////////////////////|
|/                                                           | /|
|/  / 1st |/ 2nd |/ 3rd |/ 4th |                             | /|
|/                                                           | /|
|/  midi ch : 0  |                                           | /|
|/                                                           | /|
|/    prog num : <0    >|                                    | /|
|/                                                           | /|
|/    vol (msb) : <0    >|                                   | /|
|/                                                           | /|
|/    velo : <64   >|                                        | /|
|/                                                           | /|
|/     |     |       |     |       |     |       |           | /|
|/   | | | | | | | | | | | | | | | | | | | | | | | | |       | /|
|/                                                           | /|
|/-----------------------------------------------------------+ /|
|///////////////////////////////////////////////////////////////|
+---------------------------------------------------------------+

ENTER で cui_midi にキー入力可能になります。

後は従来通り操作できるか確かめます。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <50   >|                                    |  |
||                                                           |  |
||    vol (msb) : <64   >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||     |     |       |     |       |     |       |           |  |
||   | |o| | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
|                                                               |
+---------------------------------------------------------------+

鍵盤を押してから音が聞こえるまでのレスポンスが、 若干長くなったような気がします。

まぁ、後で考えるとして、とりあえず動作してます。

ではまず、cui_kbd のカーソル位置が、何の音なのか迷子になる問題について。

オクターブの値、ドレミファの名前、ノート番号、周波数として、 表示して、設定も可能にしてみました。

オクターブの値は国際式に従いました。

ヤマハ式はさらに-1した値になるようです。

cui_midi.c の差分です

cui_midi8.patch
--- cui_midi.c-	Sat Apr 19 22:00:00 2014
+++ cui_midi.c	Sun Apr 20 22:00:00 2014
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <math.h>
 #include "cui/cui.h"
 #include "cui/scpanel.h"
 #include "cui/button.h"
@@ -239,13 +240,16 @@
 	int play_mode;
 	int cursor;
 	cui sheet;
+
+	cui oct, name, note_no, freq;
 } *cui_kbd;

 cui cui_kbd_new(cui parent, int x, int y, int w);
 void cui_kbd_init(cui obj, cui parent, int x, int y, int w);
 int cui_kbd_hdr(cui obj, int evt, int val, void *prm);
 void cui_kbd_set(cui obj, int note, int v);
-int cui_kbd_get(cui obj, int note);
+void cui_kbd_cursor_set(cui obj, int note);
+int cui_kbd_cursor_get(cui obj);


 #include "cui/key.h"
@@ -280,9 +284,10 @@
 {
 	cui_kbd p = (cui_kbd)obj;
 	int i;
+	char *name_lst[] = { "G#", "G", "F#", "F", "E", "D#", "D", "C#", "C", "B", "A#", "A", NULL };

-	cui_base_init(obj, parent, x, y, w, 2);
-	p->sheet = cui_base_new(obj, 0, 0, kbd_note_x(128), 2);
+	cui_base_init(obj, parent, x, y, w, 3);
+	p->sheet = cui_base_new(obj, 0, 1, kbd_note_x(128), 2);

 	obj->flags |= CUI_FLG_CAN_FOCUS;
 	for(i=0; i<128; i++) p->v[i] = FALSE;
@@ -291,6 +296,16 @@
 	cui_x_set(p->sheet, -kbd_note_x(p->cursor));

 	cui_bind(obj, CUI_EVT_DRAW | CUI_EVT_KEY, cui_kbd_hdr, NULL);
+
+	p->oct = cui_menu_int_new(obj, 0, 0, 2+1, 5, -1, 9);
+	p->name = cui_menu_popup_new(obj, cui_x2(p->oct), 0, 2+1, name_lst, 11);
+	p->note_no = cui_num_new(obj, cui_x2(p->name)+2, 0, 3+3, 69, 0, 127, 0);
+	p->freq = cui_num_dbl_new(obj, cui_x2(p->note_no)+2, 0, 7+3, 440.0, 8.2, 12543.9, 0.1, 0);
+
+	cui_bind(p->oct, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
+	cui_bind(p->name, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
+	cui_bind(p->note_no, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
+	cui_bind(p->freq, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
 }

 static void
@@ -354,6 +369,7 @@
 int
 cui_kbd_hdr(cui obj, int evt, int val, void *prm)
 {
+	cui ev_obj = obj;
 	cui_kbd p = (cui_kbd)obj;

 	switch(evt){
@@ -384,6 +400,8 @@
 				}
 				kbd_draw_note(obj, FALSE, p->cursor--);
 				kbd_draw_note(obj, FALSE, p->cursor);
+				cui_num_set(p->note_no, p->cursor);
+				cui_handler_call(p->note_no, CUI_EVT_BUTTON, p->cursor);
 			}
 			break;
 		case CUI_KEY_RIGHT:
@@ -395,6 +413,8 @@
 				}
 				kbd_draw_note(obj, FALSE, p->cursor++);
 				kbd_draw_note(obj, FALSE, p->cursor);
+				cui_num_set(p->note_no, p->cursor);
+				cui_handler_call(p->note_no, CUI_EVT_BUTTON, p->cursor);
 			}
 			break;
 		case CUI_KEY_UP:
@@ -413,6 +433,43 @@
 			break;
 		}
 		return TRUE;
+	case CUI_EVT_BUTTON:
+		obj = (cui)prm;
+		p = (cui_kbd)obj;
+
+		if(ev_obj == p->note_no){
+			int note = val;
+			int oct_v = (note - 9 + 12) / 12 - 1;
+			int name_v = (note - 9) % 12;
+			double fq = 440 * pow(2, (note - 69) / 12.0);
+
+			if(cui_menu_int_get(p->oct) != oct_v) cui_menu_int_set(p->oct, oct_v);
+			if(cui_menu_popup_get(p->name) != 11-name_v) cui_menu_popup_set(p->name, 11-name_v);
+			if(cui_num_dbl_get(p->freq) != fq) cui_num_dbl_set(p->freq, fq);
+			if(cui_kbd_cursor_get(obj) != note) cui_kbd_cursor_set(obj, note);
+		}else if(ev_obj == p->oct){
+			int oct_v = val;
+			int name_v = 11 - cui_menu_popup_get(p->name);
+			int note = oct_v * 12 + name_v + 9;
+			if(cui_num_get(p->note_no) != note){
+				cui_num_set(p->note_no, note);
+				cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
+			}
+		}else if(ev_obj == p->name){
+			int oct_v = cui_menu_int_get(p->oct);
+			int name_v = 11 - val;
+			int note = oct_v * 12 + name_v + 9;
+			if(cui_num_get(p->note_no) != note){
+				cui_num_set(p->note_no, note);
+				cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
+			}
+		}else if(ev_obj == p->freq){
+			double fq = cui_num_dbl_get(p->freq);
+			int note = (int)( ( log(fq/440) / log(2) ) * 12 + 69 + 0.5 );
+			cui_num_set(p->note_no, note);
+			cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
+		}else return FALSE;
+		break;
 	default:
 		return FALSE;
 	}
@@ -432,6 +489,36 @@
 {
 	cui_kbd p = (cui_kbd)obj;
 	return p->v[ note ];
+}
+
+void
+cui_kbd_cursor_set(cui obj, int note)
+{
+	cui_kbd p = (cui_kbd)obj;
+	int bak = p->cursor;
+	int x, mv = 0;
+
+	if(note < 0) note = 0;
+	if(note > 127) note = 127;
+	if(bak == note) return;
+
+	x = p->sheet->x + kbd_note_x(note);
+	if(x < 0) mv = -x;
+	else if(x > obj->w-1) mv = -(x - (obj->w-1));
+	if(mv != 0){
+		cui_x_set(p->sheet, p->sheet->x + mv);
+		cui_draw(obj);
+	}
+	p->cursor = note;
+	kbd_draw_note(obj, FALSE, bak);
+	kbd_draw_note(obj, FALSE, p->cursor);
+}
+
+int
+cui_kbd_cursor_get(cui obj)
+{
+	cui_kbd p = (cui_kbd)obj;
+	return p->cursor;
 }

 /**/

$ patch -p0 &lt; cui_midi8.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui -lm

別の端末で、cui_srv を起動して、 リサイズボックスで +5 程、幅を広げておきます。

$ cui/cui_srv -port 9071

cui_midi を起動して、cui_srv に繋ぎます。

$ ./cui_midi -conn 9071 | ./add_delta | ./prog31 -V0 -q -play

cui_srv に画面が表示されます。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <0    >|                                    |  |
||                                                           |  |
||    vol (msb) : <0    >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  5 |A |  <69 >|  <440    >|                               |  |
||     |     |       |     |       |     |       |           |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

鍵盤のカーソル位置の初期値は 5A で、ノート番号 69、周波数は 440 Hz になってます。

termin にフォーカスして ENTER。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|///////////////////////////////////////////////////////////////|
|/                                                           | /|
|/  / 1st |/ 2nd |/ 3rd |/ 4th |                             | /|
|/                                                           | /|
|/  midi ch : 0  |                                           | /|
|/                                                           | /|
|/    prog num : <0    >|                                    | /|
|/                                                           | /|
|/    vol (msb) : <0    >|                                   | /|
|/                                                           | /|
|/    velo : <64   >|                                        | /|
|/                                                           | /|
|/  5 |A |  <69 >|  <440    >|                               | /|
|/     |     |       |     |       |     |       |           | /|
|/   | | | | | | | | | | | | | | | | | | | | | | | | |       | /|
|/                                                           | /|
|///////////////////////////////////////////////////////////////|
+---------------------------------------------------------------+

オクターブの値を表示してる '5' のメニューを開いて、

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <0    >|                                    |  |
||                                                           |  |
||  9 |ol (msb) : <0    >|                                   |  |
||  8 |                                                      |  |
||  7 |elo : <64   >|                                        |  |
||  6 |                                                      |  |
||  5 |A |  <69 >|  <440    >|                               |  |
||  4 ||     |       |     |       |     |       |           |  |
||  3 || | | | | | | | | | | | | | | | | | | | | | | |       |  |
||  2 |                                                      |  |
|+--1 |------------------------------------------------------+  |
+---------------------------------------------------------------+

1オクターブ上の '6' を選択します。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <0    >|                                    |  |
||                                                           |  |
||    vol (msb) : <0    >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  6 |A |  <81 >|  <880    >|                               |  |
||     |     |       |     |       |     |       |           |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

連動してノート番号は 81 に、周波数は 880 Hz に、 鍵盤上のカーソル位置も、1オクターブ上に移動します。

鍵盤にフォーカスして ENTER。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <0    >|                                    |  |
||                                                           |  |
||    vol (msb) : <0    >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  6 |A |  <81 >|  <880    >|                               |  |
||     |     |       |     |       |     |       |           |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

左右キーでカーソルを移動すると

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <0    >|                                    |  |
||                                                           |  |
||    vol (msb) : <0    >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  6 |C |  <84 >|  <1046.50>|                               |  |
||     |     |       |     |       |     |       |           |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

連動して、音の名前、ノート番号、周波数も更新されます。

周波数の値を適当に 123 などと入力して

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <50   >|                                    |  |
||                                                           |  |
||    vol (msb) : <64   >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  6 |C |  <84 >|  <123    >|                               |  |
||     |       |     |       |     |       |     |           |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

ENTER で決定すると

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <50   >|                                    |  |
||                                                           |  |
||    vol (msb) : <64   >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  3 |B |  <47 >|  <123.470>|                               |  |
||   |     |       |     |       |     |       |     |       |  |
||   | | | | | | | | | | | | | | | | | | | | | | | | |       |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

指定の周波数に近いノート番号が設定されて、 周波数は、そのノート番号の値に補正されます。

OKです。

これで迷子になりません。

例によって

プログラム番号 50
チャンネル・ボリューム 64

に設定しておいて、

下矢印キーで鍵盤を押すと音が鳴ります。

============================ cui_srv ============================
|(conn) / 0 |                                                   |
|========================= cui_midi ==========================  |
||                                                           |  |
||  / 1st |/ 2nd |/ 3rd |/ 4th |                             |  |
||                                                           |  |
||  midi ch : 0  |                                           |  |
||                                                           |  |
||    prog num : <50   >|                                    |  |
||                                                           |  |
||    vol (msb) : <64   >|                                   |  |
||                                                           |  |
||    velo : <64   >|                                        |  |
||                                                           |  |
||  4 |F#|  <66 >|  <369.994>|                               |  |
||    |       |     | o     |     |       |     |            |  |
||  | | | | | | | | | ^ | | | | | | | | | | | | | | |        |  |
||                                                           |  |
|+-----------------------------------------------------------+  |
+---------------------------------------------------------------+

この鍵盤の部品 cui_kbd を、 CUI14 の方で取り込んでおきます

cui122_123.patch

CUIから音出ししてみる (その6)

CUIの更新を反映しつつ、機能追加してみます。

まずライブラリの更新

cui126.tgz

を引っ張ってきます。

$ rm -rf cui
$ tar xzf cui126.tgz
$ mv cui126 cui
$ (cd cui ; make)

まずは、cui_kbd のコードはライブラリに移動したので削除しておきます。

cui126.tgz のヘッダファイル cui/kbd.h に cui_kbd_get() のプロトタイプ宣言が抜けてました。

とりあえず追加しておきます。

cui/kbd.h への差分です

cui_kbd.patch
diff -urN cui-/kbd.h cui/kbd.h
--- cui-/kbd.h	Sun Apr 20 23:00:00 2014
+++ cui/kbd.h	Tue Apr 22 00:00:00 2014
@@ -18,6 +18,7 @@
 void cui_kbd_init(cui obj, cui parent, int x, int y, int w);
 int cui_kbd_hdr(cui obj, int evt, int val, void *prm);
 void cui_kbd_set(cui obj, int note, int v);
+int cui_kbd_get(cui obj, int note);
 void cui_kbd_cursor_set(cui obj, int note);
 int cui_kbd_cursor_get(cui obj);

$ cat cui_kbd.patch | (cd cui ; make clean ; patch -p1 ; make)

そろそろ 4th のタブだけあれば大丈夫そうなので、 最初の頃の画面は削除しておきます。

で、従来通りか確認です。

cui_midi.c の差分です

cui_midi9.patch
--- cui_midi.c-	Sun Apr 20 22:00:00 2014
+++ cui_midi.c	Tue Apr 22 00:00:00 2014
@@ -10,35 +10,7 @@
 #include "cui/menu.h"
 #include "cui/num.h"
 #include "cui/tab.h"
-
-typedef struct hex_in{
-	struct cui_base base;
-	cui mu, ml;
-} *hex_in;
-
-cui
-hex_in_new(cui parent, int x, int y, int v)
-{
-	cui obj = cui_alloc(sizeof(struct hex_in));
-	hex_in p = (hex_in)obj;
-	char *lst[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", NULL};
-	int w = 3;
-
-	cui_base_init(obj, parent, x, y, 0, 0);
-	p->mu = cui_menu_popup_new(obj, 0, 0, w, lst, (v>>8)&15);
-	p->ml = cui_menu_popup_new(obj, cui_x2(p->mu)+1, 0, w, lst, v&15);
-	cui_wh_fit(obj);
-	return obj;
-}
-
-int
-hex_in_get(cui obj)
-{
-	hex_in p = (hex_in)obj;
-	cui_menu_popup pu = (cui_menu_popup)p->mu;
-	cui_menu_popup pl = (cui_menu_popup)p->ml;
-	return (pu->val << 4) | pl->val;
-}
+#include "cui/kbd.h"

 FILE *fp = NULL;
 #define BUFN	4096
@@ -95,434 +67,6 @@
 	wrt();
 }

-int
-btn_hdr(cui obj, int evt, int val, void *prm)
-{
-	/* CUI_EVT_BUTTON */
-
-	char *s = cui_button_str_get(obj);
-	cui *prm_arr = (cui *)prm;
-	cui hxi = prm_arr[0];
-	cui lb = prm_arr[1];
-	char *lb_buf = cui_label_str_get(lb);
-
-	if(strcmp(s, "Add") == 0){
-		int v = hex_in_get(hxi);
-		set_char(v);
-		sprintf(lb_buf + strlen(lb_buf), "%02x ", v);
-		cui_label_str_set(lb, lb_buf);
-		return TRUE;
-	}
-	if(strcmp(s, "Out") == 0){
-		wrt();
-		lb_buf[0] = '\0';
-		cui_label_str_set(lb, lb_buf);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-struct test2{
-	cui lb_ch, mn_ch;
-	cui   lb_prog, prog, prog_out;
-	cui   lb_vol, vol, vol_out;
-	cui   lb_onoff, mn_note, lb_note_num, note_num, lb_velo, velo, note_out;
-	cui   bs_note_len;
-	cui     lb_note_len, note_len, lb_ms;
-};
-
-static int
-num_get(cui obj)
-{
-	cui_num p = (cui_num)obj;
-	return p->v;
-}
-
-int
-t2_btn_hdr(cui obj, int evt, int val, void *prm)
-{
-	/* CUI_EVT_BUTTON */
-
-	struct test2 *p = (struct test2 *)prm;
-	int ch = ((cui_menu_popup)p->mn_ch)->val;
-
-	if(obj == p->prog_out){
-		set_char2_wrt((0xc<<4) | ch, num_get(p->prog));
-		return TRUE;
-	}else if(obj == p->vol_out){
-		set_char3_wrt((0xb<<4) | ch, 7 /* vol msb */, num_get(p->vol));
-		return TRUE;
-	}else if(obj == p->note_out){
-		char *s = cui_menu_popup_str_get(p->mn_note);
-		int note_num = num_get(p->note_num);
-		int velo = num_get(p->velo);
-		if(strcmp(s, "on") == 0){
-			set_char3_wrt((9<<4) | ch, note_num, velo);
-			return TRUE;
-		}else if(strcmp(s, "off") == 0){
-			set_char3_wrt((8<<4) | ch, note_num, velo);
-			return TRUE;
-		}else if(strcmp(s, "on/off") == 0){
-			int msec = num_get(p->note_len);
-			set_char3_wrt((9<<4) | ch, note_num, velo);
-			usleep(msec * 1000);
-			set_char3_wrt((8<<4) | ch, note_num, velo);
-			return TRUE;
-		}
-	}else if(obj == p->mn_note){
-		char *s = cui_menu_popup_str_get(p->mn_note);
-		if(strcmp(s, "on/off") == 0) cui_show(p->bs_note_len);
-		else cui_hide(p->bs_note_len);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-struct test3_ch{
-	int prog, vol;
-	int note, note_num, velo, note_len;
-};
-
-struct test3{
-	cui mn_ch, prog, vol;
-	cui mn_note, note_num, velo, note_out;
-	cui bs_note_len, note_len;
-
-	int midi_ch;
-	struct test3_ch ch[16];
-};
-
-int
-t3_btn_hdr(cui obj, int evt, int val, void *prm)
-{
-	/* CUI_EVT_BUTTON */
-
-	struct test3 *p = (struct test3 *)prm;
-	struct test3_ch *ch = &p->ch[ p->midi_ch ];
-
-	if(obj == p->mn_ch){
-		cui_num_vp_set(p->prog, &ch->prog);
-		cui_num_vp_set(p->vol, &ch->vol);
-		cui_menu_popup_set(p->mn_note, ch->note); /* ! */
-		cui_handler_call(p->mn_note, CUI_EVT_BUTTON, ch->note); /* ! */
-		cui_num_vp_set(p->note_num, &ch->note_num);
-		cui_num_vp_set(p->velo, &ch->velo);
-		cui_num_vp_set(p->note_len, &ch->note_len);
-	}else if(obj == p->prog){
-		set_char2_wrt((0xc<<4) | p->midi_ch, ch->prog);
-	}else if(obj == p->vol){
-		set_char3_wrt((0xb<<4) | p->midi_ch, 7 /* vol msb */, ch->vol);
-	}else if(obj == p->mn_note){ /* ! */
-		char *s = cui_menu_popup_str_get(p->mn_note);
-		if(strcmp(s, "on/off") == 0) cui_show(p->bs_note_len);
-		else cui_hide(p->bs_note_len);
-		ch->note = cui_menu_popup_get(p->mn_note);
-	}else if(obj == p->note_out){
-		char *s = cui_menu_popup_str_get(p->mn_note);
-		if(strcmp(s, "on") == 0){
-			set_char3_wrt((9<<4) | p->midi_ch, ch->note_num, ch->velo);
-		}else if(strcmp(s, "off") == 0){
-			set_char3_wrt((8<<4) | p->midi_ch, ch->note_num, ch->velo);
-		}else if(strcmp(s, "on/off") == 0){
-			set_char3_wrt((9<<4) | p->midi_ch, ch->note_num, ch->velo);
-			usleep(ch->note_len * 1000);
-			set_char3_wrt((8<<4) | p->midi_ch, ch->note_num, ch->velo);
-		}else return FALSE;
-	}else return FALSE;
-	return TRUE;
-}
-
-/**/
-
-typedef struct cui_kbd{
-	struct cui_base base;
-	int v[128];
-	int play_mode;
-	int cursor;
-	cui sheet;
-
-	cui oct, name, note_no, freq;
-} *cui_kbd;
-
-cui cui_kbd_new(cui parent, int x, int y, int w);
-void cui_kbd_init(cui obj, cui parent, int x, int y, int w);
-int cui_kbd_hdr(cui obj, int evt, int val, void *prm);
-void cui_kbd_set(cui obj, int note, int v);
-void cui_kbd_cursor_set(cui obj, int note);
-int cui_kbd_cursor_get(cui obj);
-
-
-#include "cui/key.h"
-
-static int
-kbd_note_x(int note)
-{
-	int i = note % 12;
-	int o = note / 12;
-	return o * 14 + i + (i >= 5);
-}
-
-static int
-kbd_x_note(int x)
-{
-	int o = x / 14;
-	int i = x % 14;
-	if(i == 5 || i == 13) return -1;
-	return o * 12 + i - (i >= 5);
-}
-
-cui
-cui_kbd_new(cui parent, int x, int y, int w)
-{
-	cui obj = cui_alloc(sizeof(struct cui_kbd));
-	cui_kbd_init(obj, parent, x, y, w);
-	return obj;
-}
-
-void
-cui_kbd_init(cui obj, cui parent, int x, int y, int w)
-{
-	cui_kbd p = (cui_kbd)obj;
-	int i;
-	char *name_lst[] = { "G#", "G", "F#", "F", "E", "D#", "D", "C#", "C", "B", "A#", "A", NULL };
-
-	cui_base_init(obj, parent, x, y, w, 3);
-	p->sheet = cui_base_new(obj, 0, 1, kbd_note_x(128), 2);
-
-	obj->flags |= CUI_FLG_CAN_FOCUS;
-	for(i=0; i<128; i++) p->v[i] = FALSE;
-	p->play_mode = FALSE;
-	p->cursor = 69;
-	cui_x_set(p->sheet, -kbd_note_x(p->cursor));
-
-	cui_bind(obj, CUI_EVT_DRAW | CUI_EVT_KEY, cui_kbd_hdr, NULL);
-
-	p->oct = cui_menu_int_new(obj, 0, 0, 2+1, 5, -1, 9);
-	p->name = cui_menu_popup_new(obj, cui_x2(p->oct), 0, 2+1, name_lst, 11);
-	p->note_no = cui_num_new(obj, cui_x2(p->name)+2, 0, 3+3, 69, 0, 127, 0);
-	p->freq = cui_num_dbl_new(obj, cui_x2(p->note_no)+2, 0, 7+3, 440.0, 8.2, 12543.9, 0.1, 0);
-
-	cui_bind(p->oct, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
-	cui_bind(p->name, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
-	cui_bind(p->note_no, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
-	cui_bind(p->freq, CUI_EVT_BUTTON, cui_kbd_hdr, obj);
-}
-
-static void
-kbd_draw_x_char_attr(cui obj, int fc, int x, char *c_u, int *a_u, char *c_l, int *a_l)
-{
-	cui_kbd p = (cui_kbd)obj;
-	int note = kbd_x_note(x);
-	int blk = x & 1;
-	*c_u = note < 0 ? '|' : ' ';
-	*a_u = note >= 0 && blk ? CUI_ATTR_REVERSE : CUI_ATTR_NORMAL;
-	*c_l = blk ? '|' : ' ';
-	*a_l = CUI_ATTR_NORMAL;
-
-	if(p->play_mode) *a_l |= CUI_ATTR_ULINE;
-	if(note == p->cursor){
-		if(blk) *c_l = '^';
-		else *a_u |= CUI_ATTR_ULINE;
-	}
-	if(note >= 0 && p->v[note]){
-		if(blk) *c_u = 'o';
-		else *c_l = 'o';
-	}
-	if(!p->play_mode && fc){
-		*a_u ^= CUI_ATTR_REVERSE;
-		*a_l ^= CUI_ATTR_REVERSE;
-	}
-}
-
-static void
-kbd_draw_x(cui obj, int fc, int x)
-{
-	cui_kbd p = (cui_kbd)obj;
-	char c_u, c_l;
-	int a_u, a_l;
-	char buf[2];
-	buf[1] = '\0';
-
-	kbd_draw_x_char_attr(obj, fc, x, &c_u, &a_u, &c_l, &a_l);
-
-	buf[0] = c_u;
-	cui_draw_str(p->sheet, x, 0, buf, a_u);
-
-	buf[0] = c_l;
-	cui_draw_str(p->sheet, x, 1, buf, a_l);
-}
-
-static void
-kbd_draw_note(cui obj, int fc, int note)
-{
-	kbd_draw_x(obj, fc, kbd_note_x(note));
-}
-
-static void
-kbd_draw(cui obj, int fc)
-{
-	cui_kbd p = (cui_kbd)obj;
-	int x;
-	for(x=0; x<p->sheet->w; x++) kbd_draw_x(obj, fc, x);
-}
-
-int
-cui_kbd_hdr(cui obj, int evt, int val, void *prm)
-{
-	cui ev_obj = obj;
-	cui_kbd p = (cui_kbd)obj;
-
-	switch(evt){
-	case CUI_EVT_DRAW:
-		kbd_draw(obj, val == CUI_DRAW_FOCUS);
-		break;
-	case CUI_EVT_KEY:
-		if(!p->play_mode){
-			if(val == CUI_KEY_ENTER){
-				p->play_mode = TRUE;
-				cui_draw(obj);
-				return TRUE;
-			}
-			return FALSE;
-		}
-		/* play_mode */
-		switch(val){
-		case CUI_KEY_ENTER:
-			p->play_mode = FALSE;
-			cui_draw(obj);
-			break;
-		case CUI_KEY_LEFT:
-			if(p->cursor > 0){
-				int mv = -( p->sheet->x + kbd_note_x(p->cursor - 1) );
-				if(mv > 0){
-					cui_x_set(p->sheet, p->sheet->x + mv);
-					cui_draw(obj);
-				}
-				kbd_draw_note(obj, FALSE, p->cursor--);
-				kbd_draw_note(obj, FALSE, p->cursor);
-				cui_num_set(p->note_no, p->cursor);
-				cui_handler_call(p->note_no, CUI_EVT_BUTTON, p->cursor);
-			}
-			break;
-		case CUI_KEY_RIGHT:
-			if(p->cursor < 127){
-				int mv = p->sheet->x + kbd_note_x(p->cursor + 1) - (obj->w - 1);
-				if(mv > 0){
-					cui_x_set(p->sheet, p->sheet->x - mv);
-					cui_draw(obj);
-				}
-				kbd_draw_note(obj, FALSE, p->cursor++);
-				kbd_draw_note(obj, FALSE, p->cursor);
-				cui_num_set(p->note_no, p->cursor);
-				cui_handler_call(p->note_no, CUI_EVT_BUTTON, p->cursor);
-			}
-			break;
-		case CUI_KEY_UP:
-			if(p->v[ p->cursor ]){
-				p->v[ p->cursor ] = FALSE;
-				kbd_draw_note(obj, FALSE, p->cursor);
-				cui_handler_call(obj, CUI_EVT_BUTTON, p->cursor);
-			}
-			break;
-		case CUI_KEY_DOWN:
-			if(!p->v[ p->cursor ]){
-				p->v[ p->cursor ] = TRUE;
-				kbd_draw_note(obj, FALSE, p->cursor);
-				cui_handler_call(obj, CUI_EVT_BUTTON, p->cursor | 0x100); /* ON flag */
-			}
-			break;
-		}
-		return TRUE;
-	case CUI_EVT_BUTTON:
-		obj = (cui)prm;
-		p = (cui_kbd)obj;
-
-		if(ev_obj == p->note_no){
-			int note = val;
-			int oct_v = (note - 9 + 12) / 12 - 1;
-			int name_v = (note - 9) % 12;
-			double fq = 440 * pow(2, (note - 69) / 12.0);
-
-			if(cui_menu_int_get(p->oct) != oct_v) cui_menu_int_set(p->oct, oct_v);
-			if(cui_menu_popup_get(p->name) != 11-name_v) cui_menu_popup_set(p->name, 11-name_v);
-			if(cui_num_dbl_get(p->freq) != fq) cui_num_dbl_set(p->freq, fq);
-			if(cui_kbd_cursor_get(obj) != note) cui_kbd_cursor_set(obj, note);
-		}else if(ev_obj == p->oct){
-			int oct_v = val;
-			int name_v = 11 - cui_menu_popup_get(p->name);
-			int note = oct_v * 12 + name_v + 9;
-			if(cui_num_get(p->note_no) != note){
-				cui_num_set(p->note_no, note);
-				cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
-			}
-		}else if(ev_obj == p->name){
-			int oct_v = cui_menu_int_get(p->oct);
-			int name_v = 11 - val;
-			int note = oct_v * 12 + name_v + 9;
-			if(cui_num_get(p->note_no) != note){
-				cui_num_set(p->note_no, note);
-				cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
-			}
-		}else if(ev_obj == p->freq){
-			double fq = cui_num_dbl_get(p->freq);
-			int note = (int)( ( log(fq/440) / log(2) ) * 12 + 69 + 0.5 );
-			cui_num_set(p->note_no, note);
-			cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
-		}else return FALSE;
-		break;
-	default:
-		return FALSE;
-	}
-	return TRUE;
-}
-
-void
-cui_kbd_set(cui obj, int note, int v)
-{
-	cui_kbd p = (cui_kbd)obj;
-	p->v[ note ] = v;
-	kbd_draw_note(obj, cui_focus_get() == obj, note);
-}
-
-int
-cui_kbd_get(cui obj, int note)
-{
-	cui_kbd p = (cui_kbd)obj;
-	return p->v[ note ];
-}
-
-void
-cui_kbd_cursor_set(cui obj, int note)
-{
-	cui_kbd p = (cui_kbd)obj;
-	int bak = p->cursor;
-	int x, mv = 0;
-
-	if(note < 0) note = 0;
-	if(note > 127) note = 127;
-	if(bak == note) return;
-
-	x = p->sheet->x + kbd_note_x(note);
-	if(x < 0) mv = -x;
-	else if(x > obj->w-1) mv = -(x - (obj->w-1));
-	if(mv != 0){
-		cui_x_set(p->sheet, p->sheet->x + mv);
-		cui_draw(obj);
-	}
-	p->cursor = note;
-	kbd_draw_note(obj, FALSE, bak);
-	kbd_draw_note(obj, FALSE, p->cursor);
-}
-
-int
-cui_kbd_cursor_get(cui obj)
-{
-	cui_kbd p = (cui_kbd)obj;
-	return p->cursor;
-}
-
-/**/
-
 struct test4_ch{
 	int prog, vol, velo;
 	cui kbd;
@@ -567,126 +111,13 @@
 	int init_ret = cui_init(av);
 	cui root = cui_scpanel_new(NULL, 0, 0, 30, 10, "cui_midi");
 	cui_scpanel scp = (cui_scpanel)root;
-	cui quit = cui_base_new(scp->view, 2, 1, 0, 1); /* dmy */
-
-	cui bs1 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
-	cui hxi = hex_in_new(bs1, 0, 0, 0);
-	cui add = cui_button_new(bs1, cui_x2(hxi)+2, 0, "Add");
-	cui out = cui_button_new(bs1, cui_x2(add)+2, 0, "Out");
-	char lb_buf[ BUFN ];
-	cui lb = cui_label_new(bs1, 2, cui_y2(hxi)+1, "");
-	cui prm_arr[] = { hxi, lb };
-
-	cui bs2 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
-	char *ch_lst[] = {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15",NULL};
-	char *note_lst[] = {"on", "off", "on/off", NULL};
-	struct test2 t2;
-
-	cui bs3 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
-	struct test3 t3;
-
-	cui bs4 = cui_base_new(scp->view, quit->x, cui_y2(quit)+1, 0, 0);
+	cui bs4 = cui_base_new(scp->view, 1, 1, 0, 0);
 	struct test4 t4;
-
 	cui obj;
-
 	int div = 96;
 	int i;

-	cui tab_sheets[] = { bs1, bs2, bs3, bs4 };
-	cui_hide(bs1);
-	cui_hide(bs2);
-	cui_hide(bs3);
 	cui_hide(bs4);
-
-	lb_buf[0] = '\0';
-	cui_label_str_set(lb, lb_buf);
-
-	cui_wh_fit(bs1);
-
-	cui_bind(add, CUI_EVT_BUTTON, btn_hdr, prm_arr);
-	cui_bind(out, CUI_EVT_BUTTON, btn_hdr, prm_arr);
-
-	/* t2 */
-
-	t2.lb_ch = cui_label_new(bs2, 0, 0, "midi ch : ");
-	t2.mn_ch = cui_menu_popup_new(bs2, cui_x2(t2.lb_ch), 0, 4, ch_lst, 0);
-	t2.lb_prog = cui_label_new(bs2, 2, 2, "prog num : ");
-	t2.prog = cui_num_new(bs2, cui_x2(t2.lb_prog), 2, 8, 50, 0, 127, 0);
-	t2.prog_out = cui_button_new(bs2, cui_x2(t2.prog)+2, 2, "Out");
-	t2.lb_vol = cui_label_new(bs2, 2, 4, "vol (msb) : ");
-	t2.vol = cui_num_new(bs2, cui_x2(t2.lb_vol), 4, 8, 64, 0, 127, 0);
-	t2.vol_out = cui_button_new(bs2, cui_x2(t2.vol)+2, 4, "Out");
-	t2.lb_onoff = cui_label_new(bs2, 2, 6, "note : ");
-	t2.mn_note = cui_menu_popup_new(bs2, cui_x2(t2.lb_onoff), 6, 7, note_lst, 0);
-	t2.lb_note_num = cui_label_new(bs2, cui_x2(t2.mn_note)+2, 6, "num : ");
-	t2.note_num = cui_num_new(bs2, cui_x2(t2.lb_note_num), 6, 8, 69, 0, 127, 0);
-	t2.lb_velo = cui_label_new(bs2, cui_x2(t2.note_num)+2, 6, "velo : ");
-	t2.velo = cui_num_new(bs2, cui_x2(t2.lb_velo), 6, 8, 64, 0, 127, 0);
-	t2.note_out = cui_button_new(bs2, cui_x2(t2.velo)+2, 6, "Out");
-	t2.bs_note_len = cui_base_new(bs2, t2.mn_note->x, 7, 0, 0);
-	t2.lb_note_len = cui_label_new(t2.bs_note_len, 0, 0, "note len : ");
-	t2.note_len = cui_num_new(t2.bs_note_len, cui_x2(t2.lb_note_len), 0, 12, 500, 0, 10*1000, 0);
-	t2.lb_ms = cui_label_new(t2.bs_note_len, cui_x2(t2.note_len)+2, 0, "msec");
-
-	cui_bind(t2.prog_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
-	cui_bind(t2.vol_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
-	cui_bind(t2.note_out, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
-	cui_bind(t2.mn_note, CUI_EVT_BUTTON, t2_btn_hdr, &t2);
-
-	cui_hide(t2.bs_note_len);
-	cui_wh_fit(t2.bs_note_len);
-	cui_wh_fit(bs2);
-
-	/* t3 */
-
-	t3.midi_ch = 0;
-	for(i=0; i<16; i++){
-		t3.ch[i].prog = 0;
-		t3.ch[i].vol = 0;
-		t3.ch[i].note = 0;
-		t3.ch[i].note_num = 69;
-		t3.ch[i].velo = 64;
-		t3.ch[i].note_len = 500;
-	}
-	obj = cui_label_new(bs3, 0, 0, "midi ch : ");
-	t3.mn_ch = cui_menu_int_new(bs3, cui_x2(obj), 0, 4, 0, 0, 15);
-	cui_menu_int_vp_set(t3.mn_ch, &t3.midi_ch);
-
-	obj = cui_label_new(bs3, 2, 2, "prog num : ");
-	t3.prog = cui_num_new(bs3, cui_x2(obj), 2, 8, 0, 0, 127, 0);
-
-	obj = cui_label_new(bs3, 2, 4, "vol (msb) : ");
-	t3.vol = cui_num_new(bs3, cui_x2(obj), 4, 8, 0, 0, 127, 0);
-
-	obj = cui_label_new(bs3, 2, 6, "note : ");
-	t3.mn_note = cui_menu_popup_new(bs3, cui_x2(obj), 6, 7, note_lst, 0);
-
-	obj = cui_label_new(bs3, cui_x2(t3.mn_note)+2, 6, "num : ");
-	t3.note_num = cui_num_new(bs3, cui_x2(obj), 6, 8, 0, 0, 127, 0);
-	obj = cui_label_new(bs3, cui_x2(t3.note_num)+2, 6, "velo : ");
-	t3.velo = cui_num_new(bs3, cui_x2(obj), 6, 8, 0, 0, 127, 0);
-	t3.note_out = cui_button_new(bs3, cui_x2(t3.velo)+2, 6, "Out");
-
-	t3.bs_note_len = cui_base_new(bs3, t3.mn_note->x, 7, 0, 0);
-	obj = cui_label_new(t3.bs_note_len, 0, 0, "note len : ");
-	t3.note_len = cui_num_new(t3.bs_note_len, cui_x2(obj), 0, 12, 0, 0, 10*1000, 0);
-	cui_label_new(t3.bs_note_len, cui_x2(t3.note_len)+2, 0, "msec");
-
-	cui_hide(t3.bs_note_len);
-	cui_wh_fit(t3.bs_note_len);
-	cui_wh_fit(bs3);
-
-	cui_bind(t3.mn_ch, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
-	cui_bind(t3.prog, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
-	cui_bind(t3.vol, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
-	cui_bind(t3.note_out, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
-	cui_bind(t3.mn_note, CUI_EVT_BUTTON, t3_btn_hdr, &t3);
-
-	cui_handler_call(t3.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t3.mn_ch));
-
-	/* t4 */
-
 	t4.midi_ch = t4.bak_ch = 0;
 	for(i=0; i<16; i++){
 		t4.ch[i].prog = 0;
@@ -697,19 +128,20 @@
 		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 	}
 	obj = cui_label_new(bs4, 0, 0, "midi ch : ");
-	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), 0, 4, 0, 0, 15);
+	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), obj->y, 2+1, 0, 0, 15);
 	cui_menu_int_vp_set(t4.mn_ch, &t4.midi_ch);

-	obj = cui_label_new(bs4, 2, 2, "prog num : ");
-	t4.prog = cui_num_new(bs4, cui_x2(obj), 2, 8, 0, 0, 127, 0);
+	obj = cui_label_new(bs4, 2, cui_y2(t4.mn_ch)+1, "prog num : ");
+	t4.prog = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

-	obj = cui_label_new(bs4, 2, 4, "vol (msb) : ");
-	t4.vol = cui_num_new(bs4, cui_x2(obj), 4, 8, 0, 0, 127, 0);
+	obj = cui_label_new(bs4, 2, cui_y2(t4.prog)+1, "vol (msb) : ");
+	t4.vol = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

-	obj = cui_label_new(bs4, 2, 6, "velo : ");
-	t4.velo = cui_num_new(bs4, cui_x2(obj), 6, 8, 0, 0, 127, 0);
+	obj = cui_label_new(bs4, 2, cui_y2(t4.vol)+1, "velo : ");
+	t4.velo = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

 	cui_wh_fit(bs4);
+	cui_show(bs4);

 	cui_bind(t4.mn_ch, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 	cui_bind(t4.prog, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
@@ -717,15 +149,8 @@

 	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));

-	/**/
-
 	cui_wh_fit(scp->view);

-	cui_tab_new(scp->view, quit->x, quit->y, -1,
-		    (char *[]){"1st", "2nd", "3rd", "4th", NULL},
-		    tab_sheets, 3);
-
-	cui_wh_exp(bs1);
 	cui_wh_set(root, scp->view->w+3, scp->view->h+3);
 	cui_xy_set(scp->rszbox, root->w-1, root->h-1);

随分すっきりしました。

$ patch -p0 &lt; cui_midi9.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui -lm

まず cui_srv 起動してポート9090で待ち受けます。

$ cui/cui_srv -port 9090
========================= cui_srv ==========================
|menu|                                                     |
|                                                          |
  :

menu から shell を選んで 下矢印でフォーカス移動。

========================= cui_srv ==========================
|menu| / sh-0 |                                            |
|//////////////////////////////////////////////////////////|
|/ $                                                      /|
|/                                                        /|
  :

ENTER でshellに入力可能になります。

このshellの中から cui_midi を起動して、 cui_srv のポート9090 に繋ぎます。

========================= cui_srv ==========================
|menu| / sh-0 |                                            |
|stty erase ^H                                             |
|$ $                                                       |
|$ ./cui_midi -conn 9090 | ./add_delta | ./prog31 -V0 -q -p|
|lay                                                       |
|                                                          |
  :

コマンドを入力して ENTER。

========================= cui_srv ==========================
|menu| / sh-0 |/ 1 |                                       |
|stty erase ^H                                             |
|$ $                                                       |
|$ ./cui_midi -conn 9090 | ./add_delta | ./prog31 -V0 -q -p|
|lay                                                       |
|                                                          |
|                                                          |
  :

^Cメニューで Quit を選んで、shellのキー入力モードから抜けます。

========================= cui_srv ==========================
|menu| / sh-0 |/ 1 |                                       |
|//////////////////////////////////////////////////////////|
|/ $                                                      /|
|/ ./cui_midi -conn 9090 | ./add_delta | ./prog31 -V0 -q -/|
|/ay                                                      /|
|/                                                        /|
|/                                                        /|
  :

追加されてる タブ1 を選ぶと

========================= cui_srv ==========================
|menu| / sh-0 |/ 1 |                                       |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <0  >|                                |    |
||                                                    |    |
||   vol (msb) : <0  >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 5 |A |  <69 >|  <440    >|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | | | | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
|+----------------------------------------------------+    |
|                                                          |
|                                                          |
+----------------------------------------------------------+

下矢印キーでフォーカスを移動して

========================= cui_srv ==========================
|menu| / sh-0 |/ 1 |                                       |
|//////////////////////////////////////////////////////////|
|/                                                    |   /|
|/ midi ch : 0 |                                      |   /|
|/                                                    |   /|
|/   prog num : <0  >|                                |   /|
|/                                                    |   /|
|/   vol (msb) : <0  >|                               |   /|
|/                                                    |   /|
|/   velo : <64 >|                                    |   /|
|/                                                    |   /|
|/ 5 |A |  <69 >|  <440    >|                         |   /|
|/    |     |       |     |       |     |       |     |   /|
|/  | | | | | | | | | | | | | | | | | | | | | | | | | |   /|
|/                                                    |   /|
|/----------------------------------------------------+   /|
|/                                                        /|
|//////////////////////////////////////////////////////////|
+----------------------------------------------------------+

ENTER で、cui_midi にキー入力可能になります。

========================= cui_srv ==========================
|menu| / sh-0 |/ 1 |                                       |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <50 >|                                |    |
||                                                    |    |
||   vol (msb) : <64 >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 5 |C |  <72 >|  <523.251>|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | |o| | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
|+----------------------------------------------------+    |
|                                                          |
|                                                          |
+----------------------------------------------------------+

例によてって prog num 50、vol 64、鍵盤 ON で 音が鳴ってます。

OK。

続いて機能追加を。

ここまでは鍵盤のON/OFFですぐにノートON/ノートOFFのイベントを出してました。

この、すぐに反応する動作はImmediateモードの動作とします。

Chordモードを追加して、チェックボックスをONにすると、鍵盤ONの音を同時に鳴らすようにしてみます。

チェックボックスをOFFにすると、同時に音を止めます。

さらにArpeggioモードを追加して、ONの鍵盤を指定msecの間隔でON/OFFしていくようにしてみます。

cui_midi.c の差分です

cui_midi10.patch
--- cui_midi.c-	2014-04-22 00:00:00.000000000 +0900
+++ cui_midi.c	2014-04-22 01:00:00.000000000 +0900
@@ -70,12 +70,14 @@
 struct test4_ch{
 	int prog, vol, velo;
 	cui kbd;
+	int pmode, msec;
 };

 struct test4{
 	cui mn_ch, prog, vol, velo;
 	int midi_ch, bak_ch;
 	struct test4_ch ch[16];
+	cui pmode, on, msec, play;
 };

 int
@@ -90,17 +92,44 @@
 		cui_num_vp_set(p->prog, &ch->prog);
 		cui_num_vp_set(p->vol, &ch->vol);
 		cui_num_vp_set(p->velo, &ch->velo);
-
 		cui_hide(p->ch[ p->bak_ch ].kbd);
 		cui_show(ch->kbd);
+		cui_menu_popup_vp_set(p->pmode, &ch->pmode);
+		cui_num_vp_set(p->msec, &ch->msec);
 		p->bak_ch = p->midi_ch;
 	}else if(obj == p->prog){
 		set_char2_wrt((0xc<<4) | p->midi_ch, ch->prog);
 	}else if(obj == p->vol){
 		set_char3_wrt((0xb<<4) | p->midi_ch, 7 /* vol msb */, ch->vol);
 	}else if(obj == ch->kbd){
-		if(val & 0x100) set_char3_wrt((9<<4) | p->midi_ch, val & ~0x100, ch->velo);
-		else set_char3_wrt((8<<4) | p->midi_ch, val & ~0x100, ch->velo);
+		char *s = cui_menu_popup_str_get(p->pmode);
+		if(strcmp(s, "Immediate") == 0){
+			if(val & 0x100) set_char3_wrt((9<<4) | p->midi_ch, val & ~0x100, ch->velo);
+			else set_char3_wrt((8<<4) | p->midi_ch, val & ~0x100, ch->velo);
+		}
+	}else if(obj == p->pmode){
+		char *s = cui_menu_popup_str_get(p->pmode);
+		cui_show_hide(p->on, strcmp(s, "Chord") == 0);
+		cui_show_hide(p->msec, strcmp(s, "Arpeggio") == 0);
+		cui_show_hide(p->play, strcmp(s, "Arpeggio") == 0);
+	}else if(obj == p->on){
+		int note;
+		int hi = val ? (9<<4) : (8<<4);
+		for(note=0; note<128; note++){
+			if(!cui_kbd_get(ch->kbd, note)) continue;
+			set_char3_wrt(hi | p->midi_ch, note, ch->velo);
+		}
+	}else if(obj == p->play){
+		int note, prev;
+		prev = -1;
+		for(note=0; note<128; note++){
+			if(!cui_kbd_get(ch->kbd, note)) continue;
+			if(prev >= 0) set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
+			set_char3_wrt((9<<4) | p->midi_ch, note, ch->velo);
+			usleep(ch->msec * 1000);
+			prev = note;
+		}
+		set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
 	}else return FALSE;
 	return TRUE;
 }
@@ -126,6 +155,8 @@
 		t4.ch[i].kbd = cui_kbd_new(bs4, 0, 8, 50);
 		cui_hide(t4.ch[i].kbd);
 		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+		t4.ch[i].pmode = 0;
+		t4.ch[i].msec = 200;
 	}
 	obj = cui_label_new(bs4, 0, 0, "midi ch : ");
 	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), obj->y, 2+1, 0, 0, 15);
@@ -140,12 +171,26 @@
 	obj = cui_label_new(bs4, 2, cui_y2(t4.vol)+1, "velo : ");
 	t4.velo = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

+	t4.pmode = cui_menu_popup_new(bs4, 2, cui_y2(t4.ch[0].kbd)+1, 9+1,
+				      (char *[]){ "Immediate", "Chord", "Arpeggio", NULL}, 0);
+	t4.on = cui_ckbox_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, "ON", 0);
+	cui_hide(t4.on);
+
+	t4.msec = cui_num_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, 4+3, 200, 0, 1000, 0);
+	cui_hide(t4.msec);
+
+	t4.play = cui_button_new(bs4, cui_x2(t4.msec)+2, t4.msec->y, "play");
+	cui_hide(t4.play);
+
 	cui_wh_fit(bs4);
 	cui_show(bs4);

 	cui_bind(t4.mn_ch, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 	cui_bind(t4.prog, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 	cui_bind(t4.vol, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.pmode, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.on, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.play, CUI_EVT_BUTTON, t4_btn_hdr, &t4);

 	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));

$ patch -p0 &lt; cui_midi10.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui -lm

バックグランドで cui_midi 以下を起動しておきます。

$ ./cui_midi -srv 9091 | ./add_delta | midi_prog/prog31 -V0 -q -play &

続いて cui_srv を起動します。

$ cui/cui_srv
========================= cui_srv ==========================
|menu|                                                     |
|                                                          |
  :

menuからconnを選択し、 ポート番号 9091 を指定。

========================= cui_srv ==========================
|menu|                                                     |
| +-------------+                                          |
| |[localhost  ]|                                          |
| |<9091      >||                                          |
| |             |                                          |
| |(OK) (Cancel)|                                          |
| +-------------+                                          |
|                                                          |
  :

(OK) ENTER で接続。

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <0  >|                                |    |
||                                                    |    |
||   vol (msb) : <0  >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 5 |A |  <69 >|  <440    >|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | | | | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Immediate|                                       |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

下矢印キーでフォーカスを移して ENTER。

cui_midi にキー入力できる状態にします。

とりあえずお約束の prog num 50、vol (msb) 64 に設定。

そして追加されてる Immediate メニューを開いて

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <50 >|                                |    |
||                                                    |    |
||   vol (msb) : <64 >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 5 |A |  <69 >|  <440    >|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | | | | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Immediate|                                       |    |
||   Chord    |                                       |    |
|+---Arpeggio |---------------------------------------+    |
+----------------------------------------------------------+

Chord を選んでみます。

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <50 >|                                |    |
||                                                    |    |
||   vol (msb) : <64 >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 5 |A |  <69 >|  <440    >|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | | | | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Chord    |  [ ] ON                               |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

フォーカスを鍵盤に移してENTER。

適当にカーソルを移動し、ドミソの上で下矢印キーで押し込みます。

||                                                    |    |
|| 5 |G |  <79 >|  <783.990>|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | |o| |o| |o| | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Chord    |  [ ] ON                               |    |
||                                                    |    |

ENTERで鍵盤のモードから抜けて 下の ON チェックボックスで ENTER で一斉にノートONイベント。

||                                                    |    |
|| 5 |G |  <79 >|  <783.990>|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | |o| |o| |o| | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Chord    |  [X] ON                               |    |
||                                                    |    |

和音が鳴ります。

ENTER でチェックを外すと、ノートOFFイベントで 音が止まります。

続いて Chord のメニューを Arpeggio に変更。

||                                                    |    |
|| 5 |G |  <79 >|  <783.990>|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | |o| |o| |o| | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Arpeggio |  <200 >|  (play)                      |    |
||                                                    |    |

(play) ENTER でアルペジオで鳴ります。

||                                                    |    |
|| 5 |G |  <79 >|  <783.990>|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | |o| |o| |o| | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Arpeggio |  <200 >|  (play)                      |    |
||                                                    |    |

音の間隔 200 msec を変更して (play) で反映されます。

||                                                    |    |
|| 3 |C |  <48 >|  <130.812>|                         |    |
||      |       |     |       |     |       |     |   |    |
|| o| | | | | | |o| | | |o| | |o| |o| |o| | |o| | | | |    |
||                                                    |    |
||   Arpeggio |  <100 >|  (play)                      |    |
||                                                    |    |


CUIで音色を変えてみる

まずはアイデアをお試し

そもそも CUI14 に取り組む動機の一つが、実はこれです。

「音色のパラメータの編集をなんとか簡単に操作したい」

ようやく願いがなかいそうです。

さて、音色のパラメータへのアクセスは、どうだったか?

midi_prog/tone.h
  :
struct tone_rec{
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
	struct env_rec env;
	double level;
	struct modu_rec lfo_modu;
	struct lfo_rec lfo;
	struct modu_rec env_modu;
	struct delay_rec delay;
	int chorus;
};

struct tone_compo_rec{
	struct tone_rec *tone;
	int note; /* -1 : event note */
	double rate;
};

struct tone_compo_rec *tone_compo_get(int ch, int note, int *ret_n);
  :

midi_prog プログラムの中なら、このAPIで引っ張ってこれます。

でも、外部からチョッカイ出して、このパラメータを変更するには、その仕組みを作るのがややこしそう...

midi_prog に CUIを寄生させてみます。

main() で別スレッドを生成して、そちらでパラメータをいじる CUI の画面を出してみます。

スレッドなので、そのままメモリの内容は、まる見えです。

さて、メインスレッドが、セッセと波形データを作ってる裏で、 別スレッドが、同時にパラメータを変更しても、問題ないだろうか?

早速、簡単なCUIで試してみます。

まずはCUI14側の更新分の反映から。

cui126_127.patch
diff -urN cui126/kbd.h cui127/kbd.h
--- cui126/kbd.h	Sun Apr 20 23:00:00 2014
+++ cui127/kbd.h	Tue Apr 22 01:00:00 2014
@@ -18,6 +18,7 @@
 void cui_kbd_init(cui obj, cui parent, int x, int y, int w);
 int cui_kbd_hdr(cui obj, int evt, int val, void *prm);
 void cui_kbd_set(cui obj, int note, int v);
+int cui_kbd_get(cui obj, int note);
 void cui_kbd_cursor_set(cui obj, int note);
 int cui_kbd_cursor_get(cui obj);

cui127_128.patch
diff -urN cui127/kbd.c cui128/kbd.c
--- cui127/kbd.c	Sun Apr 20 23:00:00 2014
+++ cui128/kbd.c	Wed Apr 23 22:00:00 2014
@@ -201,7 +201,7 @@
 		if(ev_obj == p->note_no){
 			int note = val;
 			int oct_v = (note - 9 + 12) / 12 - 1;
-			int name_v = (note - 9) % 12;
+			int name_v = (note - 9 + 12) % 12;
 			double fq = 440 * pow(2, (note - 69) / 12.0);

 			if(cui_menu_int_get(p->oct) != oct_v) cui_menu_int_set(p->oct, oct_v);
@@ -214,6 +214,7 @@
 			int note = oct_v * 12 + name_v + 9;
 			if(cui_num_get(p->note_no) != note){
 				cui_num_set(p->note_no, note);
+				note = cui_num_get(p->note_no);
 				cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
 			}
 		}else if(ev_obj == p->name){
@@ -222,12 +223,14 @@
 			int note = oct_v * 12 + name_v + 9;
 			if(cui_num_get(p->note_no) != note){
 				cui_num_set(p->note_no, note);
+				note = cui_num_get(p->note_no);
 				cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
 			}
 		}else if(ev_obj == p->freq){
 			double fq = cui_num_dbl_get(p->freq);
 			int note = (int)( ( log(fq/440) / log(2) ) * 12 + 69 + 0.5 );
 			cui_num_set(p->note_no, note);
+			note = cui_num_get(p->note_no);
 			cui_handler_call(p->note_no, CUI_EVT_BUTTON, note);
 		}else return FALSE;
 		break;

cui128_129.patch
diff -urN cui128/scpanel.c cui129/scpanel.c
--- cui128/scpanel.c	Mon Feb 10 01:00:00 2014
+++ cui129/scpanel.c	Wed Apr 23 22:10:00 2014
@@ -48,22 +48,10 @@
 	return TRUE;
 }

-int
-cui_scpanel_hdr(cui obj, int evt, int val, void *prm)
+static void
+scbar_update(cui obj)
 {
-	cui_scpanel p = (cui_scpanel)prm;
-
-	/* CUI_EVT_RESIZE */
-
-	if(obj == (cui)prm){ /* scapanel */
-		cui_wh_set(p->view, obj->w-2, obj->h-2);
-		cui_xywh_set(p->vbar, obj->w-1, 1, 1, obj->h-2);
-		cui_xywh_set(p->hbar, 1, obj->h-1, obj->w-2, 1);
-		if(p->tbar && obj->w != p->tbar->w) cui_w_set(p->tbar, obj->w);
-		return FALSE;
-	}
-
-	/* sheet */
+	cui_scpanel p = (cui_scpanel)obj;

 	if(p->sheet->w > p->view->w){
 		cui_scbar_max_set(p->hbar, p->sheet->w - p->view->w);
@@ -78,7 +66,26 @@
 	}else{
 		cui_hide(p->vbar);
 	}
+}

+int
+cui_scpanel_hdr(cui obj, int evt, int val, void *prm)
+{
+	cui_scpanel p = (cui_scpanel)prm;
+
+	/* CUI_EVT_RESIZE */
+
+	if(obj == (cui)prm){ /* scapanel */
+		cui_wh_set(p->view, obj->w-2, obj->h-2);
+		cui_xywh_set(p->vbar, obj->w-1, 1, 1, obj->h-2);
+		cui_xywh_set(p->hbar, 1, obj->h-1, obj->w-2, 1);
+		if(p->tbar && obj->w != p->tbar->w) cui_w_set(p->tbar, obj->w);
+		scbar_update(obj);
+		return FALSE;
+	}
+
+	/* sheet */
+	scbar_update((cui)p);
 	return TRUE;
 }

$ cat cui126_127.patch cui127_128.patch cui128_129.patch | (cd cui ; make clean ; patch -p1 ; make)

さらに本題の前に、cui_midi の方でアルペジオに繰り返し機能を追加して、 ずっとノートON/OFFイベントを繰り返して、演奏できるようにしておきます。

cui_midi.c の差分です

cui_midi11.patch
--- cui_midi.c-	Tue Apr 22 01:00:00 2014
+++ cui_midi.c	Wed Apr 23 23:00:00 2014
@@ -11,6 +11,7 @@
 #include "cui/num.h"
 #include "cui/tab.h"
 #include "cui/kbd.h"
+#include "cui/timer.h"

 FILE *fp = NULL;
 #define BUFN	4096
@@ -71,15 +72,37 @@
 	int prog, vol, velo;
 	cui kbd;
 	int pmode, msec;
+	int arpe_note;
 };

 struct test4{
 	cui mn_ch, prog, vol, velo;
 	int midi_ch, bak_ch;
 	struct test4_ch ch[16];
-	cui pmode, on, msec, play;
+	cui pmode, on, msec, play, repeat;
 };

+static int
+repeat_hdr(cui obj, int evt, int val, void *prm)
+{
+	struct test4 *p = (struct test4 *)prm;
+	struct test4_ch *ch = &p->ch[ p->midi_ch ];
+	int i, note, prev = ch->arpe_note;
+
+
+	if(prev >= 0) set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
+	for(note=0; note<128; note++) if(cui_kbd_get(ch->kbd, note)) break;
+	if(note < 128){
+		for(i=0; i<128; i++){
+			note = (prev + 1 + i) % 128;
+			if(cui_kbd_get(ch->kbd, note)) break;
+		}
+		set_char3_wrt((9<<4) | p->midi_ch, note, ch->velo);
+		ch->arpe_note = note;
+	}
+	return TRUE;
+}
+
 int
 t4_btn_hdr(cui obj, int evt, int val, void *prm)
 {
@@ -112,6 +135,7 @@
 		cui_show_hide(p->on, strcmp(s, "Chord") == 0);
 		cui_show_hide(p->msec, strcmp(s, "Arpeggio") == 0);
 		cui_show_hide(p->play, strcmp(s, "Arpeggio") == 0);
+		cui_show_hide(p->repeat, strcmp(s, "Arpeggio") == 0);
 	}else if(obj == p->on){
 		int note;
 		int hi = val ? (9<<4) : (8<<4);
@@ -130,6 +154,8 @@
 			prev = note;
 		}
 		set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
+	}else if(obj == p->repeat){
+		cui_timer_set(obj, val ? ch->msec : -1, FALSE);
 	}else return FALSE;
 	return TRUE;
 }
@@ -157,6 +183,7 @@
 		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 		t4.ch[i].pmode = 0;
 		t4.ch[i].msec = 200;
+		t4.ch[i].arpe_note = -1;
 	}
 	obj = cui_label_new(bs4, 0, 0, "midi ch : ");
 	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), obj->y, 2+1, 0, 0, 15);
@@ -173,7 +200,7 @@

 	t4.pmode = cui_menu_popup_new(bs4, 2, cui_y2(t4.ch[0].kbd)+1, 9+1,
 				      (char *[]){ "Immediate", "Chord", "Arpeggio", NULL}, 0);
-	t4.on = cui_ckbox_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, "ON", 0);
+	t4.on = cui_ckbox_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, "ON", FALSE);
 	cui_hide(t4.on);

 	t4.msec = cui_num_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, 4+3, 200, 0, 1000, 0);
@@ -182,6 +209,9 @@
 	t4.play = cui_button_new(bs4, cui_x2(t4.msec)+2, t4.msec->y, "play");
 	cui_hide(t4.play);

+	t4.repeat = cui_ckbox_new(bs4, cui_x2(t4.play)+2, t4.play->y, "repeat", FALSE);
+	cui_hide(t4.repeat);
+
 	cui_wh_fit(bs4);
 	cui_show(bs4);

@@ -191,6 +221,8 @@
 	cui_bind(t4.pmode, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 	cui_bind(t4.on, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 	cui_bind(t4.play, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.repeat, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	cui_bind(t4.repeat, CUI_EVT_TIMER, repeat_hdr, &t4);

 	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));

つづいて midi_prog に簡単なCUIと pthread の処理を追加してみます。

prog31 から prog32 の差分です

prog32.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	Sat Apr  5 22:00:00 2014
+++ midi_prog/Makefile	Wed Apr 23 23:30:00 2014
@@ -1,7 +1,9 @@
-CC = gcc -Wall
-LIB = -lm
-TARG = prog31
+CC = gcc
+LIB = -lm -lpthread -L../cui -lcui
+TARG = prog32
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
+OBJS += pth.o
+CFLAGS += -Wall -I..

 all: $(TARG)

diff -urN midi_prog-/main.c midi_prog/main.c
--- midi_prog-/main.c	Fri Apr  4 00:00:00 2014
+++ midi_prog/main.c	Wed Apr 23 23:30:00 2014
@@ -4,6 +4,7 @@
 #include "note.h"
 #include "stat.h"
 #include "ch.h"
+#include "pth.h"

 static int
 header(void)
@@ -73,6 +74,9 @@
 	struct out_rec otr;
 	int tempo, tempo_delta_sum;
 	double tempo_sec;
+	pthread_t pth;
+
+	if(pthread_create(&pth, NULL, pth_func, av)) ERR("pthrad_create");

 	out_init(&otr, ac, av);
 	note_buf_init();
diff -urN midi_prog-/pth.c midi_prog/pth.c
--- midi_prog-/pth.c	Thu Jan  1 09:00:00 1970
+++ midi_prog/pth.c	Wed Apr 23 23:30:00 2014
@@ -0,0 +1,51 @@
+#include "pth.h"
+#include "cui/cui.h"
+#include "cui/scpanel.h"
+#include "cui/button.h"
+#include "cui/menu.h"
+#include "cui/handler.h"
+#include "tone.h"
+
+static int
+hdr(cui obj, int evt, int val, void *prm)
+{
+	cui sheet = (cui)prm;
+	int n;
+	struct tone_compo_rec *tcr = tone_compo_get(0, 69, &n);
+	struct tone_rec *tr = tcr[0].tone;
+	cui wave1 = cui_index_to_child(sheet, 1);
+	cui wave2 = cui_index_to_child(sheet, 2);
+	if(wave1->flags & CUI_FLG_HIDE){
+		cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
+		cui_show(wave1);
+		cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
+		cui_show(wave2);
+	}
+	return TRUE;
+}
+
+void *
+pth_func(void *arg)
+{
+	char **av = (char **)arg;
+	int init_ret = cui_init(av);
+
+	cui root = cui_scpanel_new(NULL, 0, 0, 20, 8, "cui_tone");
+	cui sheet = cui_scpanel_sheet_get(root);
+	cui update = cui_button_new(sheet, 0, 0, "update");
+	char *lst[] = { "sin", "saw", "square", "noise", NULL };
+	cui wave1 = cui_menu_popup_new(sheet, 0, 1, 6+1, lst, 0);
+	cui wave2 = cui_menu_popup_new(sheet, 0, 2, 6+1, lst, 0);
+
+	cui_hide(wave1);
+	cui_hide(wave2);
+	cui_bind(update, CUI_EVT_BUTTON, hdr, sheet);
+	cui_wh_fit(sheet);
+
+	cui_main(root, NULL);
+
+	cui_fini(init_ret);
+	return NULL;
+}
+
+/* EOF */
diff -urN midi_prog-/pth.h midi_prog/pth.h
--- midi_prog-/pth.h	Thu Jan  1 09:00:00 1970
+++ midi_prog/pth.h	Wed Apr 23 23:30:00 2014
@@ -0,0 +1,8 @@
+#ifndef __PTH_H__
+#define __PTH_H__
+
+#include <pthread.h>
+
+void *pth_func(void *arg);
+
+#endif

$ cat prog32.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

$ patch -p0 &lt; cui_midi11.patch
$ gcc -o cui_midi cui_midi.c -Lcui -lcui -lm
$ ./cui_midi -srv 9092 | ./add_delta | midi_prog/prog32 -srv 9093 -V0 -q -play &

バックグランドで起動しておいて

$ cui/cui_srv

menu から conn で 9092 指定して (OK) ENTER

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <0  >|                                |    |
||                                                    |    |
||   vol (msb) : <0  >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 5 |A |  <69 >|  <440    >|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | | | | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Immediate|                                       |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

下、フォーカス、ENTER で cui_midi にキー入力可能に

お約束の

prog num 50

vol (msb) 64

Immediate --> Arpeggio

鍵盤フォーカス ENTER

例えば次のように、鍵盤を押し込んでおきます

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <50 >|                                |    |
||                                                    |    |
||   vol (msb) : <64 >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 6 |C |  <84 >|  <1046.50>|                         |    |
||      |       |     |       |     |       |     |   |    |
|| o| | | |o| | |o| |o| |o| | |o| | | | | | | | | | | |    |
||                                                    |    |
||   Arpeggio |  <200 >|  (play)  [ ] repeat          |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

ENTER で鍵盤から抜けて

repeat のチェックボックスをオン

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num : <50 >|                                |    |
||                                                    |    |
||   vol (msb) : <64 >|                               |    |
||                                                    |    |
||   velo : <64 >|                                    |    |
||                                                    |    |
|| 6 |C |  <84 >|  <1046.50>|                         |    |
||      |       |     |       |     |       |     |   |    |
|| o| | | |o| | |o| |o| |o| | |o| | | | | | | | | | | |    |
||                                                    |    |
||   Arpeggio |  <200 >|  (play)  [X] repeat          |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

これでアルペジオが繰り返されます

^C でメニューを出して Quit 選択し、一旦 cui_midi の画面がら抜けます

menu から conn で 9092 指定して (OK) ENTER

追加された 1 のタブを開きます

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|===== cui_tone =====                                      |
||(update)          |                                      |
||                  |                                      |
||                  |                                      |
||                  |                                      |
||                  |                                      |
||                  |                                      |
|+------------------+                                      |
|                                                          |
  :

下、フォーカス、ENTER で cui_tone にキー入力可能に

(update) ENTER

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|===== cui_tone =====                                      |
||(update)          |                                      |
||saw   |           |                                      |
||saw   |           |                                      |
||                  |                                      |
||                  |                                      |
||                  |                                      |
|+------------------+                                      |
|                                                          |
  :

とりあえず実験なので決めうちです

2つのポップアップメニューが

ch 0 の音色の1つめ (といっても1つだけですが) の音色の構造体の、 VCO1, VCO2 の波形のタイプに対応してます

現在どちらも、ノコギリ波の指定になってるようです

上の方の saw にフォーカスして ENTER

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|===== cui_tone =====                                      |
||sin   |)          |                                      |
||saw   |           |                                      |
||square|           |                                      |
||noise |           |                                      |
||                  |                                      |
||                  |                                      |
|+------------------+                                      |
|                                                          |

square (矩形波) を選んでみると

鳴っている音色が変わりました (^_^v

下の方の saw にフォーカスして ENTER

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|===== cui_tone =====                                      |
||(update)          |                                      |
||sin   |           |                                      |
||saw   |           |                                      |
||square|           |                                      |
||noise |           |                                      |
||                  |                                      |
|+------------------+                                      |
|                                                          |

noise を選んでみると ノイジーな音に変わりました

実験成功です

スレッドで何の排他制御もしてないですが、 なんとかなってるみたいです

cui_midi の追加した repeat チェックボックスのOFF処理が 手抜きすぎるので、修正しておきます

cui_midi.c の差分です

cui_midi12.patch
--- cui_midi.c-	2014-04-23 23:00:00.000000000 +0900
+++ cui_midi.c	2014-04-24 00:00:00.000000000 +0900
@@ -82,22 +82,41 @@
 	cui pmode, on, msec, play, repeat;
 };

+static void
+note_onoff(int note, int onoff, void *prm)
+{
+	struct test4 *p = (struct test4 *)prm;
+	struct test4_ch *ch = &p->ch[ p->midi_ch ];
+	int hi = onoff ? 9 : 8;
+	int low = p->midi_ch;
+	set_char3_wrt((hi<<4) | low, note, ch->velo);
+}
+
+static void
+arpe_note_off(void *prm)
+{
+	struct test4 *p = (struct test4 *)prm;
+	struct test4_ch *ch = &p->ch[ p->midi_ch ];
+	if(ch->arpe_note < 0) return;
+	note_onoff(ch->arpe_note, FALSE, prm);
+	ch->arpe_note = -1;
+}
+
 static int
 repeat_hdr(cui obj, int evt, int val, void *prm)
 {
 	struct test4 *p = (struct test4 *)prm;
 	struct test4_ch *ch = &p->ch[ p->midi_ch ];
 	int i, note, prev = ch->arpe_note;
-

-	if(prev >= 0) set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
+	arpe_note_off(prm);
 	for(note=0; note<128; note++) if(cui_kbd_get(ch->kbd, note)) break;
 	if(note < 128){
 		for(i=0; i<128; i++){
 			note = (prev + 1 + i) % 128;
 			if(cui_kbd_get(ch->kbd, note)) break;
 		}
-		set_char3_wrt((9<<4) | p->midi_ch, note, ch->velo);
+		note_onoff(note, TRUE, prm);
 		ch->arpe_note = note;
 	}
 	return TRUE;
@@ -127,8 +146,9 @@
 	}else if(obj == ch->kbd){
 		char *s = cui_menu_popup_str_get(p->pmode);
 		if(strcmp(s, "Immediate") == 0){
-			if(val & 0x100) set_char3_wrt((9<<4) | p->midi_ch, val & ~0x100, ch->velo);
-			else set_char3_wrt((8<<4) | p->midi_ch, val & ~0x100, ch->velo);
+			int onoff = val & 0x100;
+			int note = val & ~0x100;
+			note_onoff(note, onoff, prm);
 		}
 	}else if(obj == p->pmode){
 		char *s = cui_menu_popup_str_get(p->pmode);
@@ -138,24 +158,24 @@
 		cui_show_hide(p->repeat, strcmp(s, "Arpeggio") == 0);
 	}else if(obj == p->on){
 		int note;
-		int hi = val ? (9<<4) : (8<<4);
 		for(note=0; note<128; note++){
 			if(!cui_kbd_get(ch->kbd, note)) continue;
-			set_char3_wrt(hi | p->midi_ch, note, ch->velo);
+			note_onoff(note, val, prm);
 		}
 	}else if(obj == p->play){
 		int note, prev;
 		prev = -1;
 		for(note=0; note<128; note++){
 			if(!cui_kbd_get(ch->kbd, note)) continue;
-			if(prev >= 0) set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
-			set_char3_wrt((9<<4) | p->midi_ch, note, ch->velo);
+			if(prev >= 0) note_onoff(prev, FALSE, prm);
+			note_onoff(note, TRUE, prm);
 			usleep(ch->msec * 1000);
 			prev = note;
 		}
-		set_char3_wrt((8<<4) | p->midi_ch, prev, ch->velo);
+		note_onoff(prev, FALSE, prm);
 	}else if(obj == p->repeat){
 		cui_timer_set(obj, val ? ch->msec : -1, FALSE);
+		arpe_note_off(prm);
 	}else return FALSE;
 	return TRUE;
 }

midi_prog の cui_tone から、 (update)ボタンは削除して、 chのポップアップメニューを追加しました

prog32 から prog33 の差分です

prog33.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	2014-04-23 23:30:00.000000000 +0900
+++ midi_prog/Makefile	2014-04-24 01:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog32
+TARG = prog33
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
 OBJS += pth.o
 CFLAGS += -Wall -I..
diff -urN midi_prog-/main.c midi_prog/main.c
--- midi_prog-/main.c	2014-04-23 23:30:00.000000000 +0900
+++ midi_prog/main.c	2014-04-24 01:00:00.000000000 +0900
@@ -76,8 +76,6 @@
 	double tempo_sec;
 	pthread_t pth;

-	if(pthread_create(&pth, NULL, pth_func, av)) ERR("pthrad_create");
-
 	out_init(&otr, ac, av);
 	note_buf_init();
 	stat_buf_init();
@@ -89,6 +87,9 @@
 		ch_inf[i].bend_range = 2;
 		ch_inf[i].prog =-1;
 	}
+
+	if(pthread_create(&pth, NULL, pth_func, av)) ERR("pthrad_create");
+
 	tempo = 500000;
 	tempo_delta_sum = 0;
 	tempo_sec = 0;
diff -urN midi_prog-/pth.c midi_prog/pth.c
--- midi_prog-/pth.c	2014-04-23 23:30:00.000000000 +0900
+++ midi_prog/pth.c	2014-04-24 01:00:00.000000000 +0900
@@ -7,20 +7,24 @@
 #include "tone.h"

 static int
-hdr(cui obj, int evt, int val, void *prm)
+hdr(cui ch, int evt, int val, void *prm)
 {
+	/* CUI_EVT_BUTTON */
+
 	cui sheet = (cui)prm;
+	cui bs_ch = cui_index_to_child(sheet, 2);
+	cui wave1 = cui_index_to_child(bs_ch, 0);
+	cui wave2 = cui_index_to_child(bs_ch, 1);
 	int n;
-	struct tone_compo_rec *tcr = tone_compo_get(0, 69, &n);
-	struct tone_rec *tr = tcr[0].tone;
-	cui wave1 = cui_index_to_child(sheet, 1);
-	cui wave2 = cui_index_to_child(sheet, 2);
-	if(wave1->flags & CUI_FLG_HIDE){
-		cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
-		cui_show(wave1);
-		cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
-		cui_show(wave2);
-	}
+	struct tone_compo_rec *tcr = tone_compo_get(val, -1, &n);
+	struct tone_rec *tr;
+
+	cui_show_hide(bs_ch, tcr != NULL);
+	if(tcr == NULL) return TRUE;
+
+	tr = tcr[0].tone;
+	cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
+	cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
 	return TRUE;
 }

@@ -32,15 +36,20 @@

 	cui root = cui_scpanel_new(NULL, 0, 0, 20, 8, "cui_tone");
 	cui sheet = cui_scpanel_sheet_get(root);
-	cui update = cui_button_new(sheet, 0, 0, "update");
+	cui lb = cui_label_new(sheet, 0, 0, "ch");
+	cui ch = cui_menu_int_new(sheet, cui_x2(lb)+1, lb->y, 2+1, 0, 0, 15);
+
+	cui bs_ch = cui_base_new(sheet, 0, cui_y2(ch)+1, 0, 0);
 	char *lst[] = { "sin", "saw", "square", "noise", NULL };
-	cui wave1 = cui_menu_popup_new(sheet, 0, 1, 6+1, lst, 0);
-	cui wave2 = cui_menu_popup_new(sheet, 0, 2, 6+1, lst, 0);
+
+	cui_menu_popup_new(bs_ch, 0, 0, 6+1, lst, 0);
+	cui_menu_popup_new(bs_ch, 0, 1, 6+1, lst, 0);

-	cui_hide(wave1);
-	cui_hide(wave2);
-	cui_bind(update, CUI_EVT_BUTTON, hdr, sheet);
+	cui_hide(bs_ch);
+	cui_wh_fit(bs_ch);
 	cui_wh_fit(sheet);
+	cui_bind(ch, CUI_EVT_BUTTON, hdr, sheet);
+	cui_handler_call(ch, CUI_EVT_BUTTON, 0);

 	cui_main(root, NULL);

ではVCOから

実験成功という事で、さっそくこの方式でVCOの画面から進めてみます

まずはCUI側の更新から

cui129_130.patch
diff -urN cui129/cui.c cui130/cui.c
--- cui129/cui.c	2014-04-21 22:00:00.000000000 +0900
+++ cui130/cui.c	2014-04-24 22:00:00.000000000 +0900
@@ -181,20 +181,50 @@
 	return obj;
 }

+int
+cui_xy_conv(cui parent, int v)
+{
+	cui prev;
+	int d, base, offset;
+
+	if(parent == NULL) return v;
+	if((prev = parent->children) == NULL) return v;
+	if((d = v - CUI_XY_MAGIC) < 0) return v;
+	offset = d % 100;
+	switch(d / 100){
+	case 0: /* (CUI_CHILD_X - CUI_XY_MAGIC) / 100 */
+		base = prev->x;
+		break;
+	case 1:/* (CUI_CHILD_Y - CUI_XY_MAGIC) / 100 */
+		base = prev->y;
+		break;
+	case 2:/* (CUI_CHILD_X2 - CUI_XY_MAGIC) / 100 */
+		base = cui_x2(prev);
+		break;
+	case 3:/* (CUI_CHILD_Y2 - CUI_XY_MAGIC) / 100 */
+		base = cui_y2(prev);
+		break;
+	default:
+		return v;
+	}
+	return base + offset;
+}
+
 void
 cui_base_init(cui obj, cui parent, int x, int y, int w, int h)
 {
 	obj->parent = parent;
 	obj->children = NULL;
 	obj->next = NULL;
-	obj->x = x;
-	obj->y = y;
+	obj->x = cui_xy_conv(parent, x);
+	obj->y = cui_xy_conv(parent, y);
 	obj->w = w;
 	obj->h = h;

 	if(parent) cui_base_child_add(parent, obj);
 	obj->handler_list = NULL;
 	obj->flags = 0;
+	obj->name = NULL;
 }

 void
@@ -535,6 +565,23 @@
 	return c;
 }

+cui
+cui_name_child_chain(cui obj, char *name)
+{
+	cui ret;
+	if(obj == NULL) return NULL;
+	if((ret = cui_name_child(obj, name)) != NULL) return ret;
+	return cui_name_child_chain(obj->next, name);
+}
+
+cui
+cui_name_child(cui obj, char *name)
+{
+	if(obj == NULL) return NULL;
+	if(obj->name && strcmp(obj->name, name) == 0) return obj;
+	return cui_name_child_chain(obj->children, name);
+}
+
 void
 cui_quit(void)
 {
@@ -698,6 +745,7 @@
 void
 cui_del(cui obj)
 {
+	if(obj->parent == NULL) return;
 	cui_del_from(obj, &obj->parent->children);
 }

@@ -788,4 +836,22 @@
 	}
 }

+cui
+cui_wlb_new(cui parent, int x, int y, char *s1, cui obj, char *s2)
+{
+	cui bs = cui_base_new(parent, x, y, 0, 0);
+	int bak = cui_hide_enter(obj);
+	cui lb1 = s1 ? cui_label_new(bs, 0, 0, s1) : NULL;
+
+	cui_del(obj);
+	obj->parent = bs;
+	cui_base_child_add(bs, obj);
+	cui_xy_set(obj, lb1 ? lb1->w : 0, 0);
+
+	if(s2) cui_label_new(bs, CUI_CHILD_X2, 0, s2);
+	cui_wh_fit(bs);
+	cui_hide_exit(obj, bak);
+	return bs;
+}
+
 /* EOF */
diff -urN cui129/cui.h cui130/cui.h
--- cui129/cui.h	2014-04-21 22:00:00.000000000 +0900
+++ cui130/cui.h	2014-04-24 22:00:00.000000000 +0900
@@ -7,6 +7,7 @@

 typedef struct cui_base *cui;

+#include <limits.h>
 #include "dbg.h"

 #define MSG(s) fprintf(stderr, "%s() %s L%d : %s\n", __func__, __FILE__, __LINE__, s)
@@ -30,6 +31,12 @@
 #define CUI_ATTR_ULINE		(1<<0)
 #define CUI_ATTR_REVERSE	(1<<1)

+#define CUI_XY_MAGIC		(INT_MAX - 400)
+#define CUI_CHILD_X		(CUI_XY_MAGIC + 0)
+#define CUI_CHILD_Y		(CUI_XY_MAGIC + 100)
+#define CUI_CHILD_X2		(CUI_XY_MAGIC + 200)
+#define CUI_CHILD_Y2		(CUI_XY_MAGIC + 300)
+
 typedef int (*cui_handler)(cui obj, int evt, int val, void *prm);

 #include "rect.h"
@@ -40,6 +47,7 @@
 	int x, y, w, h;
 	cui_list handler_list;
 	int flags;
+	char *name;
 };

 extern cui_rect cui_clip;
@@ -48,6 +56,7 @@
 void cui_fini(int init_ret);

 cui cui_alloc(int size);
+int cui_xy_conv(cui parent, int v);
 void cui_base_init(cui obj, cui parent, int x, int y, int w, int h);
 void cui_base_child_add(cui obj, cui child);
 cui cui_base_new(cui parent, int x, int y, int w, int h);
@@ -90,6 +99,8 @@
 int cui_child_num(cui obj);
 int cui_index(cui obj);
 cui cui_index_to_child(cui obj, int i);
+cui cui_name_child_chain(cui obj, char *name);
+cui cui_name_child(cui obj, char *name);

 cui cui_focus_get(void);
 void cui_focus_set(cui obj);
@@ -111,4 +122,7 @@
 void cui_puts_temp_fd_exit(void);
 void cui_puts(char *s);

+cui cui_wlb_new(cui parent, int x, int y, char *s1, cui obj, char *s2);
+
+
 #endif
diff -urN cui129/menu.c cui130/menu.c
--- cui129/menu.c	2014-04-21 22:00:00.000000000 +0900
+++ cui130/menu.c	2014-04-24 22:00:00.000000000 +0900
@@ -188,8 +188,9 @@
 	cui_menu_popup p = (cui_menu_popup)obj;

 	cui_menu_item_init(obj, parent, x, y, lst[val]);
-	if(w >= 0) cui_w_set(obj, w);
 	p->menu = cui_menu_new(cui_root(obj), 0, 0, lst);
+	if(w >= 0) cui_w_set(obj, w);
+	else cui_w_set(obj, p->menu->w);
 	p->val = val;
 	p->vp = NULL;
 	p->call_btn_hdr_flag = TRUE; /* ! */
@@ -206,6 +207,11 @@
 		cui_menu_popup p = (cui_menu_popup)obj;
 		cui menu = p->menu;
 		int bak = p->val;
+		if(menu->parent != cui_root(obj)){
+			if(menu->parent) cui_del(menu);
+			menu->parent = cui_root(obj);
+			cui_base_child_add(cui_root(obj), menu);
+		}
 		menu->x = cui_lx(cui_root(obj), cui_gx(obj));
 		menu->y = cui_ly(cui_root(obj), cui_gy(obj)) - p->val;
 		menu->flags &= ~CUI_FLG_HIDE;

$ cat cui129_130.patch | (cd cui ; make clean ; patch -p1 ; make)

とりあえずほぼそのままで、CUI14で追加された仕組みを使ってみます。 なので見た目は、ほぼそのままです

pthread で実行する、CUI画面用のソースファイル名を pth.c から cui_tone.c に変更しておきます

prog33 から prog34 の差分です

prog34.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	2014-04-24 01:00:00.000000000 +0900
+++ midi_prog/Makefile	2014-04-24 23:00:00.000000000 +0900
@@ -1,8 +1,8 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog33
+TARG = prog34
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
-OBJS += pth.o
+OBJS += cui_tone.o
 CFLAGS += -Wall -I..

 all: $(TARG)
diff -urN midi_prog-/cui_tone.c midi_prog/cui_tone.c
--- midi_prog-/cui_tone.c	1970-01-01 09:00:00.000000000 +0900
+++ midi_prog/cui_tone.c	2014-04-24 23:00:00.000000000 +0900
@@ -0,0 +1,65 @@
+#include "cui_tone.h"
+#include "cui/cui.h"
+#include "cui/scpanel.h"
+#include "cui/button.h"
+#include "cui/menu.h"
+#include "cui/handler.h"
+#include "tone.h"
+
+static int
+ch_hdr(cui ch, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	cui sheet = (cui)prm;
+	cui bs_ch = cui_name_child(sheet, "bs_ch");
+	cui wave1 = cui_name_child(bs_ch, "wave1");
+	cui wave2 = cui_name_child(bs_ch, "wave2");
+	int n;
+	struct tone_compo_rec *tcr = tone_compo_get(val, -1, &n);
+	struct tone_rec *tr;
+
+	cui_show_hide(bs_ch, tcr != NULL);
+	if(tcr == NULL) return TRUE;
+
+	tr = tcr[0].tone;
+	cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
+	cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
+
+	return TRUE;
+}
+
+void *
+pth_func(void *arg)
+{
+	char **av = (char **)arg;
+	int init_ret = cui_init(av);
+
+	cui root = cui_scpanel_new(NULL, 0, 0, 20, 8, "cui_tone");
+	cui sheet = cui_scpanel_sheet_get(root);
+
+	cui ch = cui_menu_int_new(NULL, 0, 0, 2+1, 0, 0, 15);
+	cui dmy1 = cui_wlb_new(sheet, 0, 0, "ch ", ch, NULL);
+
+	cui bs_ch = cui_base_new(sheet, 0, CUI_CHILD_Y2+1, 0, 0);
+	char *lst[] = { "sin", "saw", "square", "noise", NULL };
+
+	bs_ch->name = "bs_ch";
+	cui_menu_popup_new(bs_ch, 0, 0, -1, lst, 0)->name = "wave1";
+	cui_menu_popup_new(bs_ch, 0, CUI_CHILD_Y2, -1, lst, 0)->name = "wave2";
+
+	cui_hide(bs_ch);
+	cui_wh_fit(bs_ch);
+	cui_wh_fit(sheet);
+
+	cui_bind(ch, CUI_EVT_BUTTON, ch_hdr, sheet);
+	cui_handler_call(ch, CUI_EVT_BUTTON, 0);
+
+	cui_main(root, NULL);
+
+	dmy1 = NULL;
+	cui_fini(init_ret);
+	return NULL;
+}
+
+/* EOF */
diff -urN midi_prog-/cui_tone.h midi_prog/cui_tone.h
--- midi_prog-/cui_tone.h	1970-01-01 09:00:00.000000000 +0900
+++ midi_prog/cui_tone.h	2014-04-24 23:00:00.000000000 +0900
@@ -0,0 +1,8 @@
+#ifndef __CUI_TONE_H__
+#define __CUI_TONE_H__
+
+#include <pthread.h>
+
+void *pth_func(void *arg);
+
+#endif
diff -urN midi_prog-/main.c midi_prog/main.c
--- midi_prog-/main.c	2014-04-24 01:00:00.000000000 +0900
+++ midi_prog/main.c	2014-04-24 23:00:00.000000000 +0900
@@ -4,7 +4,7 @@
 #include "note.h"
 #include "stat.h"
 #include "ch.h"
-#include "pth.h"
+#include "cui_tone.h"

 static int
 header(void)
diff -urN midi_prog-/pth.c midi_prog/pth.c
--- midi_prog-/pth.c	2014-04-24 01:00:00.000000000 +0900
+++ midi_prog/pth.c	1970-01-01 09:00:00.000000000 +0900
@@ -1,60 +0,0 @@
-#include "pth.h"
-#include "cui/cui.h"
-#include "cui/scpanel.h"
-#include "cui/button.h"
-#include "cui/menu.h"
-#include "cui/handler.h"
-#include "tone.h"
-
-static int
-hdr(cui ch, int evt, int val, void *prm)
-{
-	/* CUI_EVT_BUTTON */
-
-	cui sheet = (cui)prm;
-	cui bs_ch = cui_index_to_child(sheet, 2);
-	cui wave1 = cui_index_to_child(bs_ch, 0);
-	cui wave2 = cui_index_to_child(bs_ch, 1);
-	int n;
-	struct tone_compo_rec *tcr = tone_compo_get(val, -1, &n);
-	struct tone_rec *tr;
-
-	cui_show_hide(bs_ch, tcr != NULL);
-	if(tcr == NULL) return TRUE;
-
-	tr = tcr[0].tone;
-	cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
-	cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
-	return TRUE;
-}
-
-void *
-pth_func(void *arg)
-{
-	char **av = (char **)arg;
-	int init_ret = cui_init(av);
-
-	cui root = cui_scpanel_new(NULL, 0, 0, 20, 8, "cui_tone");
-	cui sheet = cui_scpanel_sheet_get(root);
-	cui lb = cui_label_new(sheet, 0, 0, "ch");
-	cui ch = cui_menu_int_new(sheet, cui_x2(lb)+1, lb->y, 2+1, 0, 0, 15);
-
-	cui bs_ch = cui_base_new(sheet, 0, cui_y2(ch)+1, 0, 0);
-	char *lst[] = { "sin", "saw", "square", "noise", NULL };
-
-	cui_menu_popup_new(bs_ch, 0, 0, 6+1, lst, 0);
-	cui_menu_popup_new(bs_ch, 0, 1, 6+1, lst, 0);
-
-	cui_hide(bs_ch);
-	cui_wh_fit(bs_ch);
-	cui_wh_fit(sheet);
-	cui_bind(ch, CUI_EVT_BUTTON, hdr, sheet);
-	cui_handler_call(ch, CUI_EVT_BUTTON, 0);
-
-	cui_main(root, NULL);
-
-	cui_fini(init_ret);
-	return NULL;
-}
-
-/* EOF */
diff -urN midi_prog-/pth.h midi_prog/pth.h
--- midi_prog-/pth.h	2014-04-23 23:30:00.000000000 +0900
+++ midi_prog/pth.h	1970-01-01 09:00:00.000000000 +0900
@@ -1,8 +0,0 @@
-#ifndef __PTH_H__
-#define __PTH_H__
-
-#include <pthread.h>
-
-void *pth_func(void *arg);
-
-#endif

$ cat prog34.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

さらにCUI側の更新をおっかけます

cui130_131.patch
diff -urN cui130/cui.c cui131/cui.c
--- cui130/cui.c	Thu Apr 24 22:00:00 2014
+++ cui131/cui.c	Fri Apr 25 00:00:00 2014
@@ -854,4 +854,15 @@
 	return bs;
 }

+cui
+cui_wlb_name_new(cui parent, char *s1, char *name, char *s2)
+{
+	cui obj;
+
+	if(parent == NULL) return NULL;
+	if((obj = parent->children) == NULL) return NULL;
+	obj->name = name;
+	return cui_wlb_new(parent, obj->x, obj->y, s1, obj, s2);
+}
+
 /* EOF */
diff -urN cui130/cui.h cui131/cui.h
--- cui130/cui.h	Thu Apr 24 22:00:00 2014
+++ cui131/cui.h	Fri Apr 25 00:00:00 2014
@@ -123,6 +123,6 @@
 void cui_puts(char *s);

 cui cui_wlb_new(cui parent, int x, int y, char *s1, cui obj, char *s2);
-
+cui cui_wlb_name_new(cui parent, char *s1, char *name, char *s2);

 #endif
diff -urN cui130/cui_test.c cui131/cui_test.c
--- cui130/cui_test.c	Sat Apr 19 00:00:00 2014
+++ cui131/cui_test.c	Fri Apr 25 00:00:00 2014
@@ -227,19 +227,17 @@
 }

 int
-mn_btn_hdr(cui obj, int evt, int val, void *prm)
+mn_pd_hdr(cui obj, int evt, int val, void *prm)
 {
-	char *lst[] = {"Open", "Save", "Close", "Quit", NULL};
-	cui menu = cui_menu_new(obj->parent, obj->x, obj->y+1, lst);
-
-	cui_bind(menu, CUI_EVT_BUTTON, mn_hdr, prm);
-	menu->flags &= ~CUI_FLG_HIDE;
-	cui_main(menu, cui_menu_str_to_item(menu, "Open"));
-	cui_hide(menu);
-	cui_draw(menu->parent);
-	cui_del_free(menu);
+	/* CUI_EVT_BUTTON */

-	if(strcmp(cui_label_str_get((cui)prm), "Quit") == 0) cui_quit();
+	cui lb = (cui)prm;
+	char *s = cui_menu_pulldown_str_get(obj);
+	if(strcmp(s, "Quit") == 0) cui_quit();
+	else{
+		cui_label_str_set(lb, s);
+		cui_handler_call(lb, CUI_EVT_RESIZE, TRUE);
+	}
 	return TRUE;
 }

@@ -460,9 +458,10 @@
 	cui bs_termin = cui_base_new(bs_view, 0, 1, 0, 0);

 	cui base = cui_base_new(bs_view, 0, 1, 40, 20);
-	cui mn_btn = cui_menu_btn_new(base, 1, 1, "File");
-	cui etx = cui_etext_new(base, mn_btn->x, cui_y2(mn_btn), 8, "foo");
-	cui ck = cui_ckbox_new(base, cui_x2(mn_btn)+2, 1, "Show", FALSE);
+	char *mn_pd_lst[] = {"Open", "Save", "Close", "Quit", NULL};
+	cui mn_pd = cui_menu_pulldown_new(base, 1, 1, "File", mn_pd_lst);
+	cui etx = cui_etext_new(base, CUI_CHILD_X, CUI_CHILD_Y2, 8, "foo");
+	cui ck = cui_ckbox_new(base, cui_x2(mn_pd)+2, 1, "Show", FALSE);
 	cui mv_btn = cui_button_new(base, cui_x2(ck) + 2, ck->y, "move");
 	cui scl = cui_scline_new(base, cui_x2(mv_btn) + 2, mv_btn->y, 0);
 	cui mv_lb = cui_label_new(cui_scline_view_get(scl), 0, 0, "");
@@ -480,7 +479,7 @@
 	cui_wh_fit(small2);

 	cui_hide(small);
-	cui_bind(mn_btn, CUI_EVT_BUTTON, mn_btn_hdr, mv_lb);
+	cui_bind(mn_pd, CUI_EVT_BUTTON, mn_pd_hdr, mv_lb);
 	cui_bind(etx, CUI_EVT_BUTTON, etx_hdr, mv_lb);
 	cui_bind(ck, CUI_EVT_BUTTON, my_hdr2, small);
 	cui_bind(mv_btn, CUI_EVT_BUTTON, mv_hdr, mv_lb);
diff -urN cui130/menu.c cui131/menu.c
--- cui130/menu.c	Thu Apr 24 22:00:00 2014
+++ cui131/menu.c	Fri Apr 25 00:00:00 2014
@@ -151,6 +151,38 @@
 	return obj;
 }

+int
+cui_menu_hdr(cui menu, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	int *vp = (int *)prm;
+	if(vp) *vp = val;
+	cui_quit();
+	return TRUE;
+}
+
+int
+cui_menu_main(cui obj, cui menu, int *v, int dy)
+{
+	int bak = *v, ret;
+	if(menu->parent != cui_root(obj)){
+		if(menu->parent) cui_del(menu);
+		menu->parent = cui_root(obj);
+		cui_base_child_add(cui_root(obj), menu);
+	}
+
+	menu->x = cui_lx(cui_root(obj), cui_gx(obj));
+	menu->y = cui_ly(cui_root(obj), cui_gy(obj)) + dy;
+	menu->flags &= ~CUI_FLG_HIDE;
+
+	cui_main(menu, cui_index_to_child(menu, *v));
+	cui_hide(menu);
+
+	if(!(ret = (*v >= 0))) *v = bak;
+	return ret;
+}
+
 cui
 cui_menu_str_to_item(cui menu, char *s)
 {
@@ -197,7 +229,7 @@

 	cui_unbind(obj, CUI_EVT_KEY | CUI_EVT_BUTTON, cui_menu_item_hdr);
 	cui_bind(obj, CUI_EVT_KEY, cui_menu_popup_hdr, NULL);
-	cui_bind(p->menu, CUI_EVT_BUTTON, cui_menu_popup_hdr, obj);
+	cui_bind(p->menu, CUI_EVT_BUTTON, cui_menu_hdr, &p->val);
 }

 int
@@ -206,33 +238,16 @@
 	if(evt == CUI_EVT_KEY && val == CUI_KEY_ENTER){ /* menu_popup */
 		cui_menu_popup p = (cui_menu_popup)obj;
 		cui menu = p->menu;
-		int bak = p->val;
-		if(menu->parent != cui_root(obj)){
-			if(menu->parent) cui_del(menu);
-			menu->parent = cui_root(obj);
-			cui_base_child_add(cui_root(obj), menu);
-		}
-		menu->x = cui_lx(cui_root(obj), cui_gx(obj));
-		menu->y = cui_ly(cui_root(obj), cui_gy(obj)) - p->val;
-		menu->flags &= ~CUI_FLG_HIDE;
-		cui_main(menu, cui_index_to_child(menu, p->val));
-		cui_hide(menu);
-		if(p->val < 0) p->val = bak;
-		else{
-			char *s = cui_menu_item_str_get(cui_index_to_child(menu, p->val));
+		if(cui_menu_main(obj, menu, &p->val, -p->val)){
+			char *s;
+			if(p->vp) *p->vp = p->val;
+			s = cui_menu_item_str_get(cui_index_to_child(menu, p->val));
 			cui_menu_item_str_set(obj, s);
 			if(p->call_btn_hdr_flag) cui_handler_call(obj, CUI_EVT_BUTTON, p->val);
 		}
 		cui_draw(menu->parent);
-		return TRUE;
-	}else if(evt == CUI_EVT_BUTTON){ /* menu */
-		cui_menu_popup p = (cui_menu_popup)prm;
-		p->val = val;
-		if(p->vp) *p->vp = p->val;
-		cui_quit();
-		return TRUE;
-	}
-	return FALSE;
+	}else return FALSE;
+	return TRUE;
 }

 char *
@@ -435,11 +450,11 @@
 	p->btn = cui_menu_btn_new(obj, 0, 0, s);
 	cui_wh_fit(obj);
 	p->menu = cui_menu_new(cui_root(obj), 0, 0, lst);
-	p->v = -1;
+	p->v = 0;
 	p->vp = NULL;

 	cui_bind(p->btn, CUI_EVT_BUTTON, cui_menu_pulldown_hdr, obj);
-	cui_bind(p->menu, CUI_EVT_BUTTON, cui_menu_pulldown_hdr, obj);
+	cui_bind(p->menu, CUI_EVT_BUTTON, cui_menu_hdr, &p->v);
 }

 int
@@ -451,18 +466,11 @@
 	switch(evt){
 	case CUI_EVT_BUTTON:
 		if(ev_obj == p->btn){
-			int bak = p->v;
-			p->menu->x = cui_lx(cui_root(obj), cui_gx(obj));
-			p->menu->y = cui_ly(cui_root(obj), cui_gy(obj)) + 1;
-			p->menu->flags &= ~CUI_FLG_HIDE;
-			cui_main(p->menu, cui_index_to_child(p->menu, 0));
-			cui_hide(p->menu);
-			if(p->v < 0) p->v = bak;
-			else cui_handler_call(obj, CUI_EVT_BUTTON, p->v);
-		}else if(ev_obj == p->menu){
-			p->v = val;
-			if(p->vp) *p->vp = p->v;
-			cui_quit();
+			p->v = 0;
+			if(cui_menu_main(ev_obj, p->menu, &p->v, 1)){
+				if(p->vp) *p->vp = p->v;
+				cui_handler_call(obj, CUI_EVT_BUTTON, p->v);
+			}
 		}
 		break;
 	default:
diff -urN cui130/menu.h cui131/menu.h
--- cui130/menu.h	Mon Apr 21 22:00:00 2014
+++ cui131/menu.h	Fri Apr 25 00:00:00 2014
@@ -46,6 +46,8 @@
 void cui_menu_item_str_set(cui obj, char *s);

 cui cui_menu_new(cui parent, int x, int y, char **lst);
+int cui_menu_hdr(cui menu, int evt, int val, void *prm);
+
 cui cui_menu_str_to_item(cui menu, char *s);
 int cui_menu_item_num(cui obj);
 cui cui_menu_item_get(cui obj, int i);

cui131_132.patch
diff -urN cui131/menu.c cui132/menu.c
--- cui131/menu.c	2014-04-25 00:00:00.000000000 +0900
+++ cui132/menu.c	2014-04-25 01:00:00.000000000 +0900
@@ -290,6 +290,20 @@
 	if(p->vp) cui_menu_popup_set(obj, *p->vp);
 }

+void
+cui_menu_popup_list_set(cui obj, int w, char **lst)
+{
+	cui_menu_popup p = (cui_menu_popup)obj;
+
+	if(p->menu) cui_del_free(p->menu);
+	p->menu = cui_menu_new(cui_root(obj), 0, 0, lst);
+
+	if(w >= 0) cui_w_set(obj, w);
+	else cui_w_set(obj, p->menu->w);
+
+	cui_bind(p->menu, CUI_EVT_BUTTON, cui_menu_hdr, &p->val);
+}
+
 cui
 cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v)
 {
@@ -424,6 +438,8 @@
 	p->min_v = min_v;
 	p->max_v = max_v;
 	menu_int_lst_setup(obj);
+
+	cui_menu_popup_list_set(obj, -1, p->lst);
 	cui_menu_int_set(obj, p->v);
 }

diff -urN cui131/menu.h cui132/menu.h
--- cui131/menu.h	2014-04-25 00:00:00.000000000 +0900
+++ cui132/menu.h	2014-04-25 01:00:00.000000000 +0900
@@ -60,6 +60,7 @@
 void cui_menu_popup_set(cui obj, int idx);
 void cui_menu_popup_del_free(cui obj);
 void cui_menu_popup_vp_set(cui obj, int *vp);
+void cui_menu_popup_list_set(cui obj, int w, char **lst);

 cui cui_menu_int_new(cui parent, int x, int y, int w, int init_v, int min_v, int max_v);
 void cui_menu_int_init(cui obj, cui parent, int x, int y, int w, int init_v, int min_v, int max_v);

cui132_133.patch
diff -urN cui132/etext.c cui133/etext.c
--- cui132/etext.c	2014-04-11 01:00:00.000000000 +0900
+++ cui133/etext.c	2014-04-25 01:30:00.000000000 +0900
@@ -234,7 +234,7 @@
 	case CUI_EVT_DRAW:
 		attr = (val == CUI_DRAW_FOCUS) ? CUI_ATTR_ULINE : CUI_ATTR_NORMAL;
 		cui_label_attr_set(p->lb_top, attr);
-		cui_lblfix_attr_set(p->lbf, attr);
+		cui_lblfix_attr_set(p->lbf, p->mode == CUI_ETEXT_MODE_EDIT ? CUI_ATTR_NORMAL : attr);
 		cui_label_attr_set(p->lb_end, attr);
 		return TRUE;
 	case CUI_EVT_KEY:

prog34 から prog35 の差分です

prog35.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	2014-04-24 23:00:00.000000000 +0900
+++ midi_prog/Makefile	2014-04-25 22:00:00.000000000 +0900
@@ -1,6 +1,6 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog34
+TARG = prog35
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
 OBJS += cui_tone.o
 CFLAGS += -Wall -I..
diff -urN midi_prog-/cui_tone.c midi_prog/cui_tone.c
--- midi_prog-/cui_tone.c	2014-04-24 23:00:00.000000000 +0900
+++ midi_prog/cui_tone.c	2014-04-25 22:00:00.000000000 +0900
@@ -3,28 +3,78 @@
 #include "cui/scpanel.h"
 #include "cui/button.h"
 #include "cui/menu.h"
+#include "cui/num.h"
 #include "cui/handler.h"
 #include "tone.h"
+#include "ch.h"
+
+/* from tone.c */
+#define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

 static int
-ch_hdr(cui ch, int evt, int val, void *prm)
+note_i_hdr(cui obj, int evt, int val, void *prm)
 {
 	/* CUI_EVT_BUTTON */

 	cui sheet = (cui)prm;
+	cui ch = cui_name_child(sheet, "ch");
+	int ch_v = cui_menu_int_get(ch);
+	cui note = cui_name_child(sheet, "note");
+	int note_v = cui_num_get(note);
 	cui bs_ch = cui_name_child(sheet, "bs_ch");
-	cui wave1 = cui_name_child(bs_ch, "wave1");
-	cui wave2 = cui_name_child(bs_ch, "wave2");
-	int n;
-	struct tone_compo_rec *tcr = tone_compo_get(val, -1, &n);
+	cui tone_i = cui_name_child(bs_ch, "tone_i");
+	int i, n;
+	struct tone_compo_rec *tcr = tone_compo_get(ch_v, note_v, &n);
 	struct tone_rec *tr;
+	cui bs_vco = cui_name_child(bs_ch, "bs_vco");
+	cui wave1 = cui_name_child(bs_vco, "wave1");
+	cui wave2 = cui_name_child(bs_vco, "wave2");
+	cui tune = cui_name_child(bs_vco, "tune");
+	cui mix = cui_name_child(bs_vco, "mix");

-	cui_show_hide(bs_ch, tcr != NULL);
-	if(tcr == NULL) return TRUE;
+	i = cui_menu_int_get(tone_i);
+	tr = tcr[i].tone;

-	tr = tcr[0].tone;
 	cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
 	cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
+	cui_num_vp_set(tune, &tr->vco.tune);
+	cui_num_dbl_vp_set(mix, &tr->vco.mix);
+
+	return TRUE;
+}
+
+static int
+ch_note_hdr(cui obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_BUTTON */
+
+	cui sheet = (cui)prm;
+	cui ch = cui_name_child(sheet, "ch");
+	int ch_v = cui_menu_int_get(ch);
+	cui note = cui_name_child(sheet, "note");
+	int note_v = cui_num_get(note);
+	cui bs_ch = cui_name_child(sheet, "bs_ch");
+	cui prog = cui_name_child(bs_ch, "prog");
+	static char prog_buf[16];
+	cui tone_n = cui_name_child(bs_ch, "tone_n");
+	static char tone_n_buf[16];
+	cui tone_i = cui_name_child(bs_ch, "tone_i");
+	int i, n;
+	struct tone_compo_rec *tcr = tone_compo_get(ch_v, note_v, &n);
+
+	cui_show_hide(bs_ch, tcr != NULL);
+	if(tcr == NULL) return TRUE;
+
+	sprintf(prog_buf, "prog=%d", CH_PROG(val));
+	cui_label_str_set(prog, prog_buf);
+
+	sprintf(tone_n_buf, "tone_n=%d", n);
+	cui_label_str_set(tone_n, tone_n_buf);
+
+	cui_menu_int_min_max_set(tone_i, 0, n-1);
+
+	i = cui_menu_int_get(tone_i);
+	cui_handler_call(tone_i, CUI_EVT_BUTTON, i);

 	return TRUE;
 }
@@ -35,29 +85,55 @@
 	char **av = (char **)arg;
 	int init_ret = cui_init(av);

-	cui root = cui_scpanel_new(NULL, 0, 0, 20, 8, "cui_tone");
+	cui root = cui_scpanel_new(NULL, 0, 0, 40, 8, "cui_tone");
 	cui sheet = cui_scpanel_sheet_get(root);
+	cui ch, note, bs_ch, note_i, bs_vco;

-	cui ch = cui_menu_int_new(NULL, 0, 0, 2+1, 0, 0, 15);
-	cui dmy1 = cui_wlb_new(sheet, 0, 0, "ch ", ch, NULL);
-
-	cui bs_ch = cui_base_new(sheet, 0, CUI_CHILD_Y2+1, 0, 0);
-	char *lst[] = { "sin", "saw", "square", "noise", NULL };
+	char *wave_lst[] = { "sin", "saw", "square", "noise", NULL };
+
+	ch = cui_menu_int_new(sheet, 0, 0, 2+1, 0, 0, 15);
+	cui_wlb_name_new(sheet, "ch ", "ch", NULL);

+	note = cui_num_new(sheet, CUI_CHILD_X2+1, CUI_CHILD_Y, 3+3, 69, 0, 127, -1);
+	cui_wlb_name_new(sheet, "note ", "note", NULL);
+
+	bs_ch = cui_base_new(sheet, 0, CUI_CHILD_Y2+1, 0, 0);
 	bs_ch->name = "bs_ch";
-	cui_menu_popup_new(bs_ch, 0, 0, -1, lst, 0)->name = "wave1";
-	cui_menu_popup_new(bs_ch, 0, CUI_CHILD_Y2, -1, lst, 0)->name = "wave2";
+
+	cui_label_new(bs_ch, 0, 0, "prog=   ")->name = "prog";
+	cui_label_new(bs_ch, CUI_CHILD_X2+1, CUI_CHILD_Y, "tone_n=  ")->name = "tone_n";
+
+	note_i = cui_menu_int_new(bs_ch, CUI_CHILD_X2+1, CUI_CHILD_Y, 2+1, 0, 0, 0);
+	cui_wlb_name_new(bs_ch, "idx ", "tone_i", NULL);
+
+	bs_vco = cui_base_new(bs_ch, 0, CUI_CHILD_Y2, 0, 0);
+	bs_vco->name = "bs_vco";
+
+	cui_menu_popup_new(bs_vco, 0, 0, -1, wave_lst, 0);
+	cui_wlb_name_new(bs_vco, "wave1 ", "wave1", NULL);
+
+	cui_menu_popup_new(bs_vco, CUI_CHILD_X2+3, CUI_CHILD_Y, -1, wave_lst, 0);
+	cui_wlb_name_new(bs_vco, "wave2 ", "wave2", NULL);

+	cui_num_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 6+3, 0, -24000, 24000, -1);
+	cui_wlb_name_new(bs_vco, "tune ", "tune", " cent");
+
+	cui_num_dbl_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 4+3, 0, 0, 1, 0.01, -1);
+	cui_wlb_name_new(bs_vco, "mix ", "mix", NULL);
+
+	cui_wh_fit(bs_vco);
 	cui_hide(bs_ch);
 	cui_wh_fit(bs_ch);
 	cui_wh_fit(sheet);

-	cui_bind(ch, CUI_EVT_BUTTON, ch_hdr, sheet);
+	cui_bind(ch, CUI_EVT_BUTTON, ch_note_hdr, sheet);
+	cui_bind(note, CUI_EVT_BUTTON, ch_note_hdr, sheet);
+	cui_bind(note_i, CUI_EVT_BUTTON, note_i_hdr, sheet);
+
 	cui_handler_call(ch, CUI_EVT_BUTTON, 0);

 	cui_main(root, NULL);

-	dmy1 = NULL;
 	cui_fini(init_ret);
 	return NULL;
 }
diff -urN midi_prog-/tone.c midi_prog/tone.c
--- midi_prog-/tone.c	2014-01-25 00:00:00.000000000 +0900
+++ midi_prog/tone.c	2014-04-25 22:00:00.000000000 +0900
@@ -141,7 +141,8 @@
 	},{
 		PROG_DRUM, 57, /* crash cymbal 2 */
 		(struct tone_compo_rec []){
-			{ &drum_tone_inf[5], 85, 1.0 }, { NULL, }
+			{ &drum_tone_inf[5], 85, 1.0 },
+			{ NULL, }
 		}
 	},{
 		48, -1, /* timpani */
@@ -151,6 +152,7 @@
 	},{
 		50, -1, /* strings ensamble 2 */
 		(struct tone_compo_rec []){
+			{ &tone_inf[2], -1, 1.0 }, /* test */
 			{ &tone_inf[0], -1, 1.0 }, { NULL, }
 		}
 	},{

テスト用として midi_prog/tone.c のプログラム番号50の 音色の数を2つに変更してます

cui_midi.c の差分です

cui_midi13.patch
--- cui_midi.c-	2014-04-24 00:00:00.000000000 +0900
+++ cui_midi.c	2014-04-25 22:00:00.000000000 +0900
@@ -209,13 +209,13 @@
 	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), obj->y, 2+1, 0, 0, 15);
 	cui_menu_int_vp_set(t4.mn_ch, &t4.midi_ch);

-	obj = cui_label_new(bs4, 2, cui_y2(t4.mn_ch)+1, "prog num : ");
+	obj = cui_label_new(bs4, 2, cui_y2(t4.mn_ch)+1, "prog num ");
 	t4.prog = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

-	obj = cui_label_new(bs4, 2, cui_y2(t4.prog)+1, "vol (msb) : ");
+	obj = cui_label_new(bs4, 2, cui_y2(t4.prog)+1, "vol (msb) ");
 	t4.vol = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

-	obj = cui_label_new(bs4, 2, cui_y2(t4.vol)+1, "velo : ");
+	obj = cui_label_new(bs4, 2, cui_y2(t4.vol)+1, "velo ");
 	t4.velo = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);

 	t4.pmode = cui_menu_popup_new(bs4, 2, cui_y2(t4.ch[0].kbd)+1, 9+1,

まずCUIの更新から

$ cat cui130_131.patch cui131_132.patch cui132_133.patch | (cd cui ; make clean ; patch -p1 ; make)

続いて midi_prog を更新

$ cat prog35.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

最後に cui_midi を更新

$ cat cui_midi13.patch | patch -p0
$ gcc -Wall -o cui_midi cui_midi.c -Lcui -lcui -lm

動かしてみます

$ ./cui_midi -srv 9094 | ./add_delta | midi_prog/prog35 -srv 9095 -V0 -q -play &
$ cui/cui_srv

menu の conn からポート 9094 を設定して (OK) ENTER

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num <0  >|                                  |    |
||                                                    |    |
||   vol (msb) <0  >|                                 |    |
||                                                    |    |
||   velo <64 >|                                      |    |
||                                                    |    |
|| 5 |A |  <69 >|  <440    >|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | | | | | | | | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Immediate|                                       |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

下矢印 ENTER で cui_midi にキー入力可能に

上矢印、左矢印、右矢印で、prog num フォーカス ENTER 50 ENTER

下矢印、右矢印で、vol (msb) フォーカス ENTER 64 ENTER

Immediate を Arpeggio に変更

一旦 repeat フォーカスに移動してから、 上矢印で鍵盤にフォーカス移動できます (このあたり、まだまだコツが必要...)

鍵盤フォーカスで ENTER

左右でカーソル移動して、下矢印で鍵盤を押し込みます

ENTER でモードを抜けて

repeat のチェックONで音が鳴ります

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_midi ======================    |
||                                                    |    |
|| midi ch : 0 |                                      |    |
||                                                    |    |
||   prog num <50 >|                                  |    |
||                                                    |    |
||   vol (msb) <64 >|                                 |    |
||                                                    |    |
||   velo <64 >|                                      |    |
||                                                    |    |
|| 5 |G |  <79 >|  <783.990>|                         |    |
||    |     |       |     |       |     |       |     |    |
||  | |o| |o| |o| | | | | | | | | | | | | | | | | | | |    |
||                                                    |    |
||   Arpeggio |  <200 >|  (play)  [X] repeat          |    |
||                                                    |    |
|+----------------------------------------------------+    |
+----------------------------------------------------------+

^Cメニューで Quit 選択して cui_midi の入力モードから抜けます

menu から conn 選択して ポート番号9094フォーカス ENTER

右矢印、左矢印で、4にフォーカス

========================= cui_srv ==========================
|menu| / 0 |                                               |
|=+-------------+====== cui_midi ======================    |
|||[localhost  ]|                                     |    |
|||<9094      >||                                     |    |
|||             |                                     |    |
|||(OK)o(Cancel)| >|                                  |    |
||+-------------+                                     |    |
  :

上矢印でメニューを開いて 5 を選択 ENTER さらに ENTER で 9095に決定

(OK) ENTER

タブ 1 を選択

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|=============== cui_tone ===============                  |
||ch 0 | note <69 >|                    |                  |
||                                      |                  |
||                                      |                  |
||                                      |                  |
||                                      |                  |
||                                      |                  |
|+--------------------------------------+                  |
  :

下矢印 ENTER で cui_tone にキー入力可能なモードに入ります

note 69 の '&gt;' にでもフォーカスして ENTERしてみると

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|=============== cui_tone ===============                  |
||ch 0 | note <70 >|                    |                  |
||                                      |                  |
||prog=0   tone_n=2  idx 0|             |                  |
||wave1 square|   wave2 square|         |                  |
||                tune <10    >| cent   |                  |
||                mix <0.5 >|           |                  |
|+--------------------------------------+                  |
  :

tone_n が 2 になってます

idx を 1 に変更すると

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|=============== cui_tone ===============                  |
||ch 0 | note <70 >|                    |                  |
||                                      |                  |
||prog=0   tone_n=2  idx 1|             |                  |
||wave1 saw   |   wave2 saw   |         |                  |
||                tune <12    >| cent   |                  |
||                mix <0.5 >|           |                  |
|+--------------------------------------+                  |
  :

反映されます

wave1 の波形を saw から noise に変更してみると

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|=============== cui_tone ===============                  |
||ch 0 | note <70 >|                    |                  |
||                                      |                  |
||prog=0sin   |_n=2  idx 1|             |                  |
||wave1 saw   |   wave2 saw   |         |                  |
||      square|   tune <12    >| cent   |                  |
||      noise |   mix <0.5 >|           |                  |
|+--------------------------------------+                  |
  :

音がノイジーに変わります

tune の値を適当に変えてみても、音色の変化が分かります

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|=============== cui_tone ===============                  |
||ch 0 | note <70 >|                    |                  |
||                                      |                  |
||prog=0   tone_n=2  idx 1|             |                  |
||wave1 noise |   wave2 saw   |         |                  |
||                tune <-500  >| cent   |                  |
||                mix <0.5 >|           |                  |
|+--------------------------------------+                  |
  :

OKです

次はフィルタの設定

毎度CUIの更新からです

cui133_134.patch
diff -urN cui133/cui.c cui134/cui.c
--- cui133/cui.c	Fri Apr 25 00:00:00 2014
+++ cui134/cui.c	Sat Apr 26 22:00:00 2014
@@ -726,6 +726,8 @@
 			cui_focus_set(obj);
 			cui_draw(focus);
 			cui_draw(obj);
+		}else{
+			cui_puts("\033H"); /* ! */
 		}
 	}
 	main_stack = sp->prev;
diff -urN cui133/cui_srv.c cui134/cui_srv.c
--- cui133/cui_srv.c	Mon Apr 21 22:00:00 2014
+++ cui134/cui_srv.c	Sat Apr 26 22:00:00 2014
@@ -56,7 +56,7 @@
 	int y = cui_y2(cui_tab_scline(p->tab));
 	cui t = (cfd == -1) ?
 		cui_terminal_new(p->view, 0, 0, 0, 0):
-		cui_termin_new(p->view, 0, 0, 0, 0, cfd);
+		cui_termin_new(p->view, 0, 0, 0, 0, cfd, TRUE);

 	static int cnt = 0;
 	char buf[16];
diff -urN cui133/cui_test.c cui134/cui_test.c
--- cui133/cui_test.c	Fri Apr 25 00:00:00 2014
+++ cui134/cui_test.c	Sat Apr 26 22:00:00 2014
@@ -533,7 +533,7 @@
 	for(i=1; i<ac; i++) if(strcmp(av[i], "-termin") == 0) break;
 	if(i+1 < ac){
 		int sock = cui_conn(av[i+1]);
-		cui_termin_new(bs_termin, 0, 0, bs_termin->w, bs_termin->h, sock);
+		cui_termin_new(bs_termin, 0, 0, bs_termin->w, bs_termin->h, sock, FALSE);
 	}

 	cui_w_set(bs_view, cui_x2(((cui_tab)tab)->scl));
diff -urN cui133/term.c cui134/term.c
--- cui133/term.c	Mon Apr 21 22:00:00 2014
+++ cui134/term.c	Sat Apr 26 22:00:00 2014
@@ -211,6 +211,12 @@
 	if(p->esc_buf_n == 1) return;
 	if(p->esc_buf_n == 2){
 		if(c == '[') return;
+		if(c == 'H'){
+			cui_term_move(obj, 0, 0);
+			p->esc_buf_n = 0;
+			cui_quit(); /* ! */
+			return;
+		}
 		esc_flush(obj);
 		return;
 	}
@@ -422,15 +428,15 @@
 /**/

 cui
-cui_termin_new(cui parent, int x, int y, int w, int h, int sock)
+cui_termin_new(cui parent, int x, int y, int w, int h, int sock, int auto_focus)
 {
 	cui obj = cui_alloc(sizeof(struct cui_termin));
-	cui_termin_init(obj, parent, x, y, w, h, sock);
+	cui_termin_init(obj, parent, x, y, w, h, sock, auto_focus);
 	return obj;
 }

 void
-cui_termin_init(cui obj, cui parent, int x, int y, int w, int h, int sock)
+cui_termin_init(cui obj, cui parent, int x, int y, int w, int h, int sock, int auto_focus)
 {
 	cui_termin p = (cui_termin)obj;
 	int i;
@@ -448,6 +454,7 @@

 	p->sock = sock;
 	p->in_key_mode = FALSE;
+	p->auto_focus = auto_focus;

 	cui_bind(obj, CUI_EVT_DRAW | CUI_EVT_KEY | CUI_EVT_RESIZE | CUI_EVT_READABLE, cui_termin_hdr, NULL);

@@ -479,6 +486,14 @@

 	switch(evt){
 	case CUI_EVT_DRAW:
+		if(p->auto_focus){
+			if(!p->in_key_mode && val == CUI_DRAW_FOCUS){
+				p->in_key_mode = TRUE;
+				cui_main(obj, obj);
+				p->in_key_mode = FALSE;
+			}
+			break;
+		}
 		for(i=0; i<4; i++) cui_show_hide(p->fill[i], (!p->in_key_mode && val == CUI_DRAW_FOCUS));
 		break;
 	case CUI_EVT_KEY:
@@ -616,7 +631,7 @@
 	char *cmd = "stty erase ^H\r";

 	boot_sh(obj);
-	cui_termin_init(obj, parent, x, y, w, h, p->sock);
+	cui_termin_init(obj, parent, x, y, w, h, p->sock, FALSE);
 	if(write(p->sock, cmd, strlen(cmd)) <0) ERR("write");
 }

diff -urN cui133/term.h cui134/term.h
--- cui133/term.h	Mon Apr 21 22:00:00 2014
+++ cui134/term.h	Sat Apr 26 22:00:00 2014
@@ -23,6 +23,7 @@
 	cui fill[4];
 	int sock;
 	int in_key_mode;
+	int auto_focus;
 } *cui_termin;

 typedef struct cui_terminal{
@@ -53,8 +54,8 @@

 /**/

-cui cui_termin_new(cui parent, int x, int y, int w, int h, int sock);
-void cui_termin_init(cui obj, cui parent, int x, int y, int w, int h, int sock);
+cui cui_termin_new(cui parent, int x, int y, int w, int h, int sock, int auto_focus);
+void cui_termin_init(cui obj, cui parent, int x, int y, int w, int h, int sock, int auto_focus);
 int cui_termin_hdr(cui obj, int evt, int val, void *prm);
 void cui_termin_kill(cui obj);
 void cui_termin_del_free(cui obj);

cui_midi.c の差分です

../midi_src/cui_midi14.patch
--- cui_midi.c-	Fri Apr 25 22:00:00 2014
+++ cui_midi.c	Sat Apr 26 23:00:00 2014
@@ -184,11 +184,9 @@
 main(int ac, char **av)
 {
 	int init_ret = cui_init(av);
-	cui root = cui_scpanel_new(NULL, 0, 0, 30, 10, "cui_midi");
-	cui_scpanel scp = (cui_scpanel)root;
-	cui bs4 = cui_base_new(scp->view, 1, 1, 0, 0);
+	cui root = cui_scpanel_new(NULL, 0, 0, 45, 11, "cui_midi");
+	cui bs4 = cui_scpanel_sheet_get(root);
 	struct test4 t4;
-	cui obj;
 	int div = 96;
 	int i;

@@ -198,38 +196,41 @@
 		t4.ch[i].prog = 0;
 		t4.ch[i].vol = 0;
 		t4.ch[i].velo = 64;
-		t4.ch[i].kbd = cui_kbd_new(bs4, 0, 8, 50);
-		cui_hide(t4.ch[i].kbd);
-		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
 		t4.ch[i].pmode = 0;
 		t4.ch[i].msec = 200;
 		t4.ch[i].arpe_note = -1;
 	}
-	obj = cui_label_new(bs4, 0, 0, "midi ch : ");
-	t4.mn_ch = cui_menu_int_new(bs4, cui_x2(obj), obj->y, 2+1, 0, 0, 15);
+	t4.mn_ch = cui_menu_int_new(bs4, 0, 0, 2+1, 0, 0, 15);
+	cui_wlb_name_new(bs4, "ch ", NULL, NULL);
 	cui_menu_int_vp_set(t4.mn_ch, &t4.midi_ch);

-	obj = cui_label_new(bs4, 2, cui_y2(t4.mn_ch)+1, "prog num ");
-	t4.prog = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);
+	t4.prog = cui_num_new(bs4, CUI_CHILD_X, CUI_CHILD_Y2+1, 3+3, 0, 0, 127, 0);
+	cui_wlb_name_new(bs4, "prog ", NULL, NULL);

-	obj = cui_label_new(bs4, 2, cui_y2(t4.prog)+1, "vol (msb) ");
-	t4.vol = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);
+	t4.vol = cui_num_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, 3+3, 0, 0, 127, 0);
+	cui_wlb_name_new(bs4, "vol (msb) ", NULL, NULL);

-	obj = cui_label_new(bs4, 2, cui_y2(t4.vol)+1, "velo ");
-	t4.velo = cui_num_new(bs4, cui_x2(obj), obj->y, 3+3, 0, 0, 127, 0);
+	t4.velo = cui_num_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, 3+3, 0, 0, 127, 0);
+	cui_wlb_name_new(bs4, "velo ", NULL, NULL);

-	t4.pmode = cui_menu_popup_new(bs4, 2, cui_y2(t4.ch[0].kbd)+1, 9+1,
+	for(i=0; i<16; i++){
+		t4.ch[i].kbd = cui_kbd_new(bs4, 0, cui_y2(t4.velo->parent)+1, 40);
+		cui_hide(t4.ch[i].kbd);
+		cui_bind(t4.ch[i].kbd, CUI_EVT_BUTTON, t4_btn_hdr, &t4);
+	}
+
+	t4.pmode = cui_menu_popup_new(bs4, 0, CUI_CHILD_Y2+1, 9+1,
 				      (char *[]){ "Immediate", "Chord", "Arpeggio", NULL}, 0);
-	t4.on = cui_ckbox_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, "ON", FALSE);
+	t4.on = cui_ckbox_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, "ON", FALSE);
 	cui_hide(t4.on);

-	t4.msec = cui_num_new(bs4, cui_x2(t4.pmode)+2, t4.pmode->y, 4+3, 200, 0, 1000, 0);
+	t4.msec = cui_num_new(bs4, CUI_CHILD_X, CUI_CHILD_Y, 4+3, 200, 0, 1000, 0);
 	cui_hide(t4.msec);

-	t4.play = cui_button_new(bs4, cui_x2(t4.msec)+2, t4.msec->y, "play");
+	t4.play = cui_button_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, "play");
 	cui_hide(t4.play);

-	t4.repeat = cui_ckbox_new(bs4, cui_x2(t4.play)+2, t4.play->y, "repeat", FALSE);
+	t4.repeat = cui_ckbox_new(bs4, CUI_CHILD_X2+2, CUI_CHILD_Y, "repeat", FALSE);
 	cui_hide(t4.repeat);

 	cui_wh_fit(bs4);
@@ -245,11 +246,6 @@
 	cui_bind(t4.repeat, CUI_EVT_TIMER, repeat_hdr, &t4);

 	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));
-
-	cui_wh_fit(scp->view);
-
-	cui_wh_set(root, scp->view->w+3, scp->view->h+3);
-	cui_xy_set(scp->rszbox, root->w-1, root->h-1);

 	fp = stdout;

prog35 から prog36 の差分です

../midi_src/prog36.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	Fri Apr 25 22:00:00 2014
+++ midi_prog/Makefile	Sat Apr 26 23:00:00 2014
@@ -1,6 +1,6 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog35
+TARG = prog36
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
 OBJS += cui_tone.o
 CFLAGS += -Wall -I..
diff -urN midi_prog-/cui_tone.c midi_prog/cui_tone.c
--- midi_prog-/cui_tone.c	Fri Apr 25 22:00:00 2014
+++ midi_prog/cui_tone.c	Sat Apr 26 23:30:00 2014
@@ -21,16 +21,22 @@
 	int ch_v = cui_menu_int_get(ch);
 	cui note = cui_name_child(sheet, "note");
 	int note_v = cui_num_get(note);
-	cui bs_ch = cui_name_child(sheet, "bs_ch");
-	cui tone_i = cui_name_child(bs_ch, "tone_i");
+	cui tone_i = cui_name_child(sheet, "tone_i");
 	int i, n;
 	struct tone_compo_rec *tcr = tone_compo_get(ch_v, note_v, &n);
 	struct tone_rec *tr;
-	cui bs_vco = cui_name_child(bs_ch, "bs_vco");
-	cui wave1 = cui_name_child(bs_vco, "wave1");
-	cui wave2 = cui_name_child(bs_vco, "wave2");
-	cui tune = cui_name_child(bs_vco, "tune");
-	cui mix = cui_name_child(bs_vco, "mix");
+
+	cui wave1 = cui_name_child(sheet, "wave1");
+	cui wave2 = cui_name_child(sheet, "wave2");
+	cui tune = cui_name_child(sheet, "tune");
+	cui mix = cui_name_child(sheet, "mix");
+
+	cui filt1 = cui_name_child(sheet, "filt1");
+	cui filt2 = cui_name_child(sheet, "filt2");
+	cui freq1 = cui_name_child(sheet, "freq1");
+	cui freq2 = cui_name_child(sheet, "freq2");
+	cui Q1 = cui_name_child(sheet, "Q1");
+	cui Q2 = cui_name_child(sheet, "Q2");

 	i = cui_menu_int_get(tone_i);
 	tr = tcr[i].tone;
@@ -40,6 +46,13 @@
 	cui_num_vp_set(tune, &tr->vco.tune);
 	cui_num_dbl_vp_set(mix, &tr->vco.mix);

+	cui_menu_popup_vp_set(filt1, &tr->fl1.type);
+	cui_num_dbl_vp_set(freq1, &tr->fl1.freq);
+	cui_num_dbl_vp_set(Q1, &tr->fl1.Q);
+	cui_menu_popup_vp_set(filt2, &tr->fl2.type);
+	cui_num_dbl_vp_set(freq2, &tr->fl2.freq);
+	cui_num_dbl_vp_set(Q2, &tr->fl2.Q);
+
 	return TRUE;
 }

@@ -54,18 +67,18 @@
 	cui note = cui_name_child(sheet, "note");
 	int note_v = cui_num_get(note);
 	cui bs_ch = cui_name_child(sheet, "bs_ch");
-	cui prog = cui_name_child(bs_ch, "prog");
+	cui prog = cui_name_child(sheet, "prog");
 	static char prog_buf[16];
-	cui tone_n = cui_name_child(bs_ch, "tone_n");
+	cui tone_n = cui_name_child(sheet, "tone_n");
 	static char tone_n_buf[16];
-	cui tone_i = cui_name_child(bs_ch, "tone_i");
+	cui tone_i = cui_name_child(sheet, "tone_i");
 	int i, n;
 	struct tone_compo_rec *tcr = tone_compo_get(ch_v, note_v, &n);

 	cui_show_hide(bs_ch, tcr != NULL);
 	if(tcr == NULL) return TRUE;

-	sprintf(prog_buf, "prog=%d", CH_PROG(val));
+	sprintf(prog_buf, "prog=%d", CH_PROG(ch_v));
 	cui_label_str_set(prog, prog_buf);

 	sprintf(tone_n_buf, "tone_n=%d", n);
@@ -85,16 +98,17 @@
 	char **av = (char **)arg;
 	int init_ret = cui_init(av);

-	cui root = cui_scpanel_new(NULL, 0, 0, 40, 8, "cui_tone");
+	cui root = cui_scpanel_new(NULL, 0, 0, 44, 14, "cui_tone");
 	cui sheet = cui_scpanel_sheet_get(root);
-	cui ch, note, bs_ch, note_i, bs_vco;
+	cui ch, note, bs_ch, note_i, bs_vco, bs_filt;

 	char *wave_lst[] = { "sin", "saw", "square", "noise", NULL };
+	char *filt_lst[] = { "OFF", "LPF", "HPF", "BPF", NULL };

 	ch = cui_menu_int_new(sheet, 0, 0, 2+1, 0, 0, 15);
 	cui_wlb_name_new(sheet, "ch ", "ch", NULL);

-	note = cui_num_new(sheet, CUI_CHILD_X2+1, CUI_CHILD_Y, 3+3, 69, 0, 127, -1);
+	note = cui_num_new(sheet, CUI_CHILD_X2+1, CUI_CHILD_Y, 3+3, 69, 0, 127, 0);
 	cui_wlb_name_new(sheet, "note ", "note", NULL);

 	bs_ch = cui_base_new(sheet, 0, CUI_CHILD_Y2+1, 0, 0);
@@ -106,7 +120,9 @@
 	note_i = cui_menu_int_new(bs_ch, CUI_CHILD_X2+1, CUI_CHILD_Y, 2+1, 0, 0, 0);
 	cui_wlb_name_new(bs_ch, "idx ", "tone_i", NULL);

-	bs_vco = cui_base_new(bs_ch, 0, CUI_CHILD_Y2, 0, 0);
+	/* vco */
+
+	bs_vco = cui_base_new(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0);
 	bs_vco->name = "bs_vco";

 	cui_menu_popup_new(bs_vco, 0, 0, -1, wave_lst, 0);
@@ -115,13 +131,41 @@
 	cui_menu_popup_new(bs_vco, CUI_CHILD_X2+3, CUI_CHILD_Y, -1, wave_lst, 0);
 	cui_wlb_name_new(bs_vco, "wave2 ", "wave2", NULL);

-	cui_num_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 6+3, 0, -24000, 24000, -1);
+	cui_num_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 6+3, 0, -24000, 24000, 0);
 	cui_wlb_name_new(bs_vco, "tune ", "tune", " cent");

-	cui_num_dbl_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 4+3, 0, 0, 1, 0.01, -1);
+	cui_num_dbl_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 4+3, 0, 0, 1, 0.01, 0);
 	cui_wlb_name_new(bs_vco, "mix ", "mix", NULL);

 	cui_wh_fit(bs_vco);
+
+	/* filt */
+
+	bs_filt = cui_base_new(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0);
+	bs_filt->name = "bs_filt";
+
+	cui_menu_popup_new(bs_filt, 0, 0, -1, filt_lst, 0);
+	cui_wlb_name_new(bs_filt, "filter1 ", "filt1", NULL);
+
+	cui_num_dbl_new(bs_filt, CUI_CHILD_X, CUI_CHILD_Y2, 7+3, 3000, 20, 20000, 0.1, 0);
+	cui_wlb_name_new(bs_filt, "freq1 ", "freq1", " Hz");
+
+	cui_num_dbl_new(bs_filt, CUI_CHILD_X, CUI_CHILD_Y2, 5+3, 1, 0, 10, 0.01, 0);
+	cui_wlb_name_new(bs_filt, "reso1 (Q1) ", "Q1", NULL);
+
+	cui_wh_fit(bs_filt);
+
+	cui_menu_popup_new(bs_filt, bs_filt->w+2, 0, -1, filt_lst, 0);
+	cui_wlb_name_new(bs_filt, "filter2 ", "filt2", NULL);
+
+	cui_num_dbl_new(bs_filt, CUI_CHILD_X, CUI_CHILD_Y2, 7+3, 3000, 20, 20000, 0.1, 0);
+	cui_wlb_name_new(bs_filt, "freq2 ", "freq2", " Hz");
+
+	cui_num_dbl_new(bs_filt, CUI_CHILD_X, CUI_CHILD_Y2, 5+3, 1, 0, 10, 0.01, 0);
+	cui_wlb_name_new(bs_filt, "reso2 (Q2) ", "Q2", NULL);
+
+	cui_wh_fit(bs_filt);
+
 	cui_hide(bs_ch);
 	cui_wh_fit(bs_ch);
 	cui_wh_fit(sheet);

$ cat cui133_134.patch | (cd cui ; make clean ; patch -p1 ; make)
$ cat prog36.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat cui_midi14.patch | patch -p0
$ gcc -Wall -o cui_midi -cui_midi.c -Lcui -lcui -lm
$ cui/cui_srv -port 9098

別端末から

$ ./cui_midi -conn 9098 | ./add_delta | midi_prog/prog36 -conn 9098 -V0 -q -play

cui_srv の画面

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|================= cui_midi ==================             |
||ch 0 |                                     |             |
||                                           |             |
||prog <0  >|  vol (msb) <0  >|  velo <64 >| |             |
||                                           |             |
||5 |A |  <69 >|  <440    >|                 |             |
||   |     |       |     |       |     |     |             |
|| | | | | | | | | | | | | | | | | | | | |   |             |
||                                           |             |
||Immediate|                                 |             |
|+-------------------------------------------+             |
|                                                          |
|                                                          |
|                                                          |
|                                                          |
|                                                          |
|                                                          |
+----------------------------------------------------------+

prog 50

vol 64

Immediate を Arpeggio に

鍵盤 いくつか ON

repeat チェック ON

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|================= cui_midi ==================             |
||ch 0 |                                     |             |
||                                           |             |
||prog <50 >|  vol (msb) <64 >|  velo <64 >| |             |
||                                           |             |
||6 |C |  <84 >|  <1046.50>|                 |             |
||   |     |       |     |       |     |     |             |
|| | |o| | | |o| | |o| | | | | | | | | | |   |             |
||                                           |             |
||Arpeggio |  <200 >|  (play)  [X] repeat    |             |
|+-------------------------------------------+             |
  :

タブ1 ENTER

note 69 の '&gt;' ENTER

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|================= cui_tone =================              |
||ch 0 | note <70 >|                        |              |
||                                          |              |
||prog=50  tone_n=2  idx 0|                 |              |
||                                          |              |
||wave1 square|   wave2 square|             |              |
||                tune <10    >| cent       |              |
||                mix <0.5 >|               |              |
||                                          |              |
||filter1 LPF|         filter2 OFF|         |              |
||freq1 <2000   >| Hz  freq2 <20     >| Hz  |              |
||reso1 (Q1) <4    >|  reso2 (Q2) <0    >|  |              |
||                                          |              |
|+------------------------------------------+              |
  :

freq1 を 2000 から 4000 に変更

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|================= cui_tone =================              |
||ch 0 | note <70 >|                        |              |
||                                          |              |
||prog=50  tone_n=2  idx 0|                 |              |
||                                          |              |
||wave1 square|   wave2 square|             |              |
||                tune <10    >| cent       |              |
||                mix <0.5 >|               |              |
||                                          |              |
||filter1 LPF|         filter2 OFF|         |              |
||freq1 <4000   >| Hz  freq2 <20     >| Hz  |              |
||reso1 (Q1) <4    >|  reso2 (Q2) <0    >|  |              |
||                                          |              |
|+------------------------------------------+              |
  :

音が変わります OK

続いてエンベロープ以降

毎度CUIの更新からです

../cui14/src/cui134_135.patch
diff -urN cui134/panel.c cui135/panel.c
--- cui134/panel.c	Sun Feb  9 00:00:00 2014
+++ cui135/panel.c	Sun Apr 27 22:00:00 2014
@@ -45,4 +45,58 @@
 	return TRUE;
 }

+/**/
+
+cui
+cui_title_panel_new(cui parent, int x, int y, int w, int h, char *s)
+{
+	cui obj = cui_alloc(sizeof(struct cui_title_panel));
+	cui_title_panel_init(obj, parent, x, y, w, h, s);
+	return obj;
+}
+
+void
+cui_title_panel_init(cui obj, cui parent, int x, int y, int w, int h, char *s)
+{
+	cui_title_panel p = (cui_title_panel)obj;
+
+	cui_panel_init(obj, parent, x, y, w, h);
+	p->lb = cui_label_new(obj, 3, 0, s);
+	p->sheet = cui_base_new(obj, 1, 1, w-2, h-2);
+	cui_bind(obj, CUI_EVT_RESIZE, cui_title_panel_hdr, obj);
+	cui_bind(p->sheet, CUI_EVT_RESIZE, cui_title_panel_hdr, obj);
+}
+
+int
+cui_title_panel_hdr(cui ev_obj, int evt, int val, void *prm)
+{
+	/* CUI_EVT_RESIZE */
+
+	cui obj = (cui)prm;
+	cui_title_panel p = (cui_title_panel)obj;
+	cui sheet = p->sheet;
+
+	if(ev_obj == obj){
+		if(sheet->w != obj->w-2 || sheet->h != obj->h-2) cui_wh_set(sheet, obj->w-2, obj->h-2);
+		return FALSE; /* for cui_panel_hdr() */
+	}else if(ev_obj == sheet){
+		if(obj->w != sheet->w+2 || obj->h != sheet->h+2) cui_wh_set(obj, sheet->w+2, sheet->h+2);
+	}else return FALSE;
+	return TRUE;
+}
+
+cui
+cui_title_panel_sheet_get(cui obj)
+{
+	cui_title_panel p = (cui_title_panel)obj;
+	return p->sheet;
+}
+
+cui
+cui_title_panel_new_sheet(cui parent, int x, int y, int w, int h, char *s)
+{
+	return cui_title_panel_sheet_get(
+		cui_title_panel_new(parent, x, y, w, h, s));
+}
+
 /* EOF */
diff -urN cui134/panel.h cui135/panel.h
--- cui134/panel.h	Fri Feb  7 22:00:00 2014
+++ cui135/panel.h	Sun Apr 27 22:00:00 2014
@@ -8,8 +8,19 @@
 	cui fill[8];
 } *cui_panel;

+typedef struct cui_title_panel{
+	struct cui_panel panel;
+	cui lb, sheet;
+} *cui_title_panel;
+
 cui cui_panel_new(cui parent, int x, int y, int w, int h);
 void cui_panel_init(cui obj, cui parent, int x, int y, int w, int h);
 int cui_panel_hdr(cui obj, int evt, int val, void *prm);
+
+cui cui_title_panel_new(cui parent, int x, int y, int w, int h, char *s);
+void cui_title_panel_init(cui obj, cui parent, int x, int y, int w, int h, char *s);
+int cui_title_panel_hdr(cui obj, int evt, int val, void *prm);
+cui cui_title_panel_sheet_get(cui obj);
+cui cui_title_panel_new_sheet(cui parent, int x, int y, int w, int h, char *s);

 #endif
diff -urN cui134/scpanel.c cui135/scpanel.c
--- cui134/scpanel.c	Wed Apr 23 22:10:00 2014
+++ cui135/scpanel.c	Sun Apr 27 22:00:00 2014
@@ -81,6 +81,9 @@
 		cui_xywh_set(p->hbar, 1, obj->h-1, obj->w-2, 1);
 		if(p->tbar && obj->w != p->tbar->w) cui_w_set(p->tbar, obj->w);
 		scbar_update(obj);
+		if(p->rszbox->x != obj->w-1 || p->rszbox->y != obj->h-1){
+			cui_xy_set(p->rszbox, obj->w-1, obj->h-1);
+		}
 		return FALSE;
 	}

prog36 から prog37 の差分です

../midi_src/prog37.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	Sat Apr 26 23:00:00 2014
+++ midi_prog/Makefile	Sun Apr 27 22:00:00 2014
@@ -1,6 +1,6 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog36
+TARG = prog37
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
 OBJS += cui_tone.o
 CFLAGS += -Wall -I..
diff -urN midi_prog-/cui_tone.c midi_prog/cui_tone.c
--- midi_prog-/cui_tone.c	Sat Apr 26 23:30:00 2014
+++ midi_prog/cui_tone.c	Sun Apr 27 22:00:00 2014
@@ -38,6 +38,32 @@
 	cui Q1 = cui_name_child(sheet, "Q1");
 	cui Q2 = cui_name_child(sheet, "Q2");

+	cui attack = cui_name_child(sheet, "attack");
+	cui decay = cui_name_child(sheet, "decay");
+	cui sustain = cui_name_child(sheet, "sustain");
+	cui release = cui_name_child(sheet, "release");
+
+	cui level = cui_name_child(sheet, "level");
+
+	cui lfo_mp1 = cui_name_child(sheet, "lfo_mp1");
+	cui lfo_mp2 = cui_name_child(sheet, "lfo_mp2");
+	cui lfo_mf1 = cui_name_child(sheet, "lfo_mf1");
+	cui lfo_mf2 = cui_name_child(sheet, "lfo_mf2");
+
+	cui lfo_wave = cui_name_child(sheet, "lfo_wave");
+	cui lfo_freq = cui_name_child(sheet, "lfo_freq");
+	cui lfo_delay = cui_name_child(sheet, "lfo_delay");
+
+	cui env_mp1 = cui_name_child(sheet, "env_mp1");
+	cui env_mp2 = cui_name_child(sheet, "env_mp2");
+	cui env_mf1 = cui_name_child(sheet, "env_mf1");
+	cui env_mf2 = cui_name_child(sheet, "env_mf2");
+
+	cui delay = cui_name_child(sheet, "delay");
+	cui delay_sec = cui_name_child(sheet, "delay_sec");
+	cui delay_gain = cui_name_child(sheet, "delay_gain");
+	cui chorus  = cui_name_child(sheet, "chorus");
+
 	i = cui_menu_int_get(tone_i);
 	tr = tcr[i].tone;

@@ -53,6 +79,32 @@
 	cui_num_dbl_vp_set(freq2, &tr->fl2.freq);
 	cui_num_dbl_vp_set(Q2, &tr->fl2.Q);

+	cui_num_dbl_vp_set(attack, &tr->env.attack);
+	cui_num_dbl_vp_set(decay, &tr->env.decay);
+	cui_num_dbl_vp_set(sustain, &tr->env.sustain);
+	cui_num_dbl_vp_set(release, &tr->env.release);
+
+	cui_num_dbl_vp_set(level, &tr->level);
+
+	cui_num_vp_set(lfo_mp1, &tr->lfo_modu.pitch1);
+	cui_num_vp_set(lfo_mp2, &tr->lfo_modu.pitch2);
+	cui_num_vp_set(lfo_mf1, &tr->lfo_modu.filter1);
+	cui_num_vp_set(lfo_mf2, &tr->lfo_modu.filter2);
+
+	cui_menu_popup_vp_set(lfo_wave, &tr->lfo.wave);
+	cui_num_dbl_vp_set(lfo_freq, &tr->lfo.freq);
+	cui_num_dbl_vp_set(lfo_delay, &tr->lfo.delay);
+
+	cui_num_vp_set(env_mp1, &tr->env_modu.pitch1);
+	cui_num_vp_set(env_mp2, &tr->env_modu.pitch2);
+	cui_num_vp_set(env_mf1, &tr->env_modu.filter1);
+	cui_num_vp_set(env_mf2, &tr->env_modu.filter2);
+
+	cui_menu_popup_vp_set(delay, &tr->delay.onoff);
+	cui_num_dbl_vp_set(delay_sec, &tr->delay.sec);
+	cui_num_dbl_vp_set(delay_gain, &tr->delay.gain);
+	cui_menu_popup_vp_set(chorus, &tr->chorus);
+
 	return TRUE;
 }

@@ -98,12 +150,14 @@
 	char **av = (char **)arg;
 	int init_ret = cui_init(av);

-	cui root = cui_scpanel_new(NULL, 0, 0, 44, 14, "cui_tone");
+	cui root = cui_scpanel_new(NULL, 0, 0, 44, 20, "cui_tone");
 	cui sheet = cui_scpanel_sheet_get(root);
-	cui ch, note, bs_ch, note_i, bs_vco, bs_filt;
+	cui ch, note, bs_ch, note_i, bs_vco, bs_filt, bs_env;
+	cui bs_lfo_modu, bs_lfo, bs_env_modu, bs_delay;

 	char *wave_lst[] = { "sin", "saw", "square", "noise", NULL };
 	char *filt_lst[] = { "OFF", "LPF", "HPF", "BPF", NULL };
+	char *onoff_lst[] = { "OFF", "ON", NULL };

 	ch = cui_menu_int_new(sheet, 0, 0, 2+1, 0, 0, 15);
 	cui_wlb_name_new(sheet, "ch ", "ch", NULL);
@@ -122,8 +176,7 @@

 	/* vco */

-	bs_vco = cui_base_new(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0);
-	bs_vco->name = "bs_vco";
+	bs_vco = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " VCO ");

 	cui_menu_popup_new(bs_vco, 0, 0, -1, wave_lst, 0);
 	cui_wlb_name_new(bs_vco, "wave1 ", "wave1", NULL);
@@ -141,8 +194,7 @@

 	/* filt */

-	bs_filt = cui_base_new(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0);
-	bs_filt->name = "bs_filt";
+	bs_filt = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " Filter ");

 	cui_menu_popup_new(bs_filt, 0, 0, -1, filt_lst, 0);
 	cui_wlb_name_new(bs_filt, "filter1 ", "filt1", NULL);
@@ -166,9 +218,101 @@

 	cui_wh_fit(bs_filt);

+	/* env */
+
+	bs_env = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " ENV ");
+
+	cui_num_dbl_new(bs_env, 0, 0, 5+3, 0, 0, 10, 0.01, 0);
+	cui_wlb_name_new(bs_env, "attack  ", "attack", " sec");
+
+	cui_num_dbl_new(bs_env, CUI_CHILD_X, CUI_CHILD_Y2, 5+3, 0, 0, 10, 0.01, 0);
+	cui_wlb_name_new(bs_env, "decay   ", "decay", " sec");
+
+	cui_num_dbl_new(bs_env, CUI_CHILD_X, CUI_CHILD_Y2, 5+3, 0, 0, 1, 0.01, 0);
+	cui_wlb_name_new(bs_env, "sustain ", "sustain", " (0..1)");
+
+	cui_num_dbl_new(bs_env, CUI_CHILD_X, CUI_CHILD_Y2, 5+3, 0, 0, 10, 0.01, 0);
+	cui_wlb_name_new(bs_env, "release ", "release", " sec");
+
+	cui_wh_fit(bs_env);
+
+	cui_num_dbl_new(bs_ch, 0, CUI_CHILD_Y2+1, 5+3, 0, 0, 10, 0.01, 0);
+	cui_wlb_name_new(bs_ch, "level ", "level", NULL);
+
+	/* lfo modu */
+
+	bs_lfo_modu = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " LFO modu ");
+
+	cui_num_new(bs_lfo_modu, 0, 0, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_lfo_modu, "pitch1  ", "lfo_mp1", " cent");
+
+	cui_num_new(bs_lfo_modu, CUI_CHILD_X2+2, CUI_CHILD_Y, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_lfo_modu, "pitch2  ", "lfo_mp2", " cent");
+
+	cui_num_new(bs_lfo_modu, 0, CUI_CHILD_Y2, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_lfo_modu, "filter1 ", "lfo_mf1", " cent");
+
+	cui_num_new(bs_lfo_modu, CUI_CHILD_X2+2, CUI_CHILD_Y, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_lfo_modu, "filter2 ", "lfo_mf2", " cent");
+
+	cui_wh_fit(bs_lfo_modu);
+
+	/* lfo */
+
+	bs_lfo = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " LFO ");
+
+ 	cui_menu_popup_new(bs_lfo, 0, 0, -1, wave_lst, 0);
+	cui_wlb_name_new(bs_lfo, "wave ", "lfo_wave", NULL);
+
+	cui_num_dbl_new(bs_lfo, CUI_CHILD_X2+2, CUI_CHILD_Y, 7+3, 4, 0, 20000, 0.1, 0);
+	cui_wlb_name_new(bs_lfo, "freq ", "lfo_freq", " Hz");
+
+	cui_num_dbl_new(bs_lfo, CUI_CHILD_X2+2, CUI_CHILD_Y, 4+3, 0, 0, 10, 0.1, 0);
+	cui_wlb_name_new(bs_lfo, "delay ", "lfo_delay", " sec");
+
+	cui_wh_fit(bs_lfo);
+
+	/* env modu */
+
+	bs_env_modu = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " ENV modu ");
+
+	cui_num_new(bs_env_modu, 0, 0, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_env_modu, "pitch1  ", "env_mp1", " cent");
+
+	cui_num_new(bs_env_modu, CUI_CHILD_X2+2, CUI_CHILD_Y, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_env_modu, "pitch2  ", "env_mp2", " cent");
+
+	cui_num_new(bs_env_modu, 0, CUI_CHILD_Y2, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_env_modu, "filter1 ", "env_mf1", " cent");
+
+	cui_num_new(bs_env_modu, CUI_CHILD_X2+2, CUI_CHILD_Y, 6+3, 0, -24000, 24000, 0);
+	cui_wlb_name_new(bs_env_modu, "filter2 ", "env_mf2", " cent");
+
+	cui_wh_fit(bs_env_modu);
+
+	/* delay */
+
+	bs_delay = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " delay ");
+
+ 	cui_menu_popup_new(bs_delay, 0, 0, -1, onoff_lst, 0);
+	cui_wlb_name_new(bs_delay, NULL, "delay", NULL);
+
+	cui_num_dbl_new(bs_delay, CUI_CHILD_X2+2, CUI_CHILD_Y, 4+3, 0, 0, 10, 0.1, 0);
+	cui_wlb_name_new(bs_delay, NULL, "delay_sec", " sec");
+
+	cui_num_dbl_new(bs_delay, CUI_CHILD_X2+2, CUI_CHILD_Y, 4+3, 0, 0, 10, 0.1, 0);
+	cui_wlb_name_new(bs_delay, "gain ", "delay_gain", NULL);
+
+	cui_wh_fit(bs_delay);
+
+	cui_menu_popup_new(bs_ch, 0, CUI_CHILD_Y2+1, -1, onoff_lst, 0);
+	cui_wlb_name_new(bs_ch, "chorus ", "chorus", NULL);
+
 	cui_hide(bs_ch);
 	cui_wh_fit(bs_ch);
 	cui_wh_fit(sheet);
+
+	cui_wh_set(root, sheet->w+2, sheet->h+2);

 	cui_bind(ch, CUI_EVT_BUTTON, ch_note_hdr, sheet);
 	cui_bind(note, CUI_EVT_BUTTON, ch_note_hdr, sheet);

$ cat cui134_135.patch | (cd cui ; make clean ; patch -p1 ; make)
$ cat prog37.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ gcc -Wall -o cui_midi cui_midi.c -Lcui -lcui -lm
$ cui/cui_srv -port 9099

別端末から

$ ./cui_midi -conn 9099 | ./add_delta | midi_prog/prog37 -conn 9099 -V0 -q -play

cui_srv の画面

お約束の

prog 50

vol 64

Immediate を Arpeggio に

鍵盤 いくつか ON

repeat チェック ON

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|================= cui_midi ==================             |
||ch 0 |                                     |             |
||                                           |             |
||prog <50 >|  vol (msb) <64 >|  velo <64 >| |             |
||                                           |             |
||6 |C |  <84 >|  <1046.50>|                 |             |
||   |     |       |     |       |     |     |             |
|| | |o| | | |o| | |o| | | | | | | | | | |   |             |
||                                           |             |
||Arpeggio |  <200 >|  (play)  [X] repeat    |             |
|+-------------------------------------------+             |
  :

そして cui_srv のリサイズボックスでサイズを広げておいて

タブ1 ENTER

note 69 の '>' ENTER

  :
|====================== cui_tone =======================   |
||ch 0 | note <70 >|                                   |   |
||                                                     |   |
||prog=50  tone_n=2  idx 0|                            |   |
||                                                     |   |
||+-- VCO ----------------------------+                |   |
|||wave1 square|   wave2 square|      |                |   |
|||                tune <10    >| cent|                |   |
|||                mix <0.5 >|        |                |   |
||+-----------------------------------+                |   |
||                                                     |   |
||+-- Filter ------------------------------+           |   |
|||filter1 LPF|         filter2 OFF|       |           |   |
|||freq1 <2000   >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <4    >|  reso2 (Q2) <0    >||           |   |
||+----------------------------------------+           |   |
||                                                     |   |
||+-- ENV ----------------+                            |   |
|||attack  <0.01 >| sec   |                            |   |
|||decay   <0.2  >| sec   |                            |   |
|||sustain <0.8  >| (0..1)|                            |   |
|||release <0.3  >| sec   |                            |   |
||+-----------------------+                            |   |
||                                                     |   |
||level <1    >|                                       |   |
||                                                     |   |
||+-- LFO modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <1     >| cent|     |   |
|||filter1 <5     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||                                                     |   |
||+-- LFO --------------------------------------------+|   |
|||wave sin   |  freq <4      >| Hz  delay <0.3 >| sec||   |
||+---------------------------------------------------+|   |
||                                                     |   |
||+-- ENV modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <0     >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||                                                     |   |
||+-- delay ----------------------+                    |   |
|||ON |  <0.2 >| sec  gain <0.4 >||                    |   |
||+-------------------------------+                    |   |
||                                                     |   |
||chorus OFF|                                          |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

とりあえず全部並べてみました

さすがに縦長すぎて、画面からは見出しがちです。

rootのスクロールパネルのサイズを小さくして、 スクロールさせようと試すと、スクロールパネルの不具合が出ました。

CUI側に修正を入れて反映します。

cui_midi や midi_prog/prog38 の起動オプションで

-w 幅 -h 高さ

として指定できるようにしました

../cui14/src/cui135_136.patch
diff -urN cui135/ckbox.c cui136/ckbox.c
--- cui135/ckbox.c	Sun Feb  9 01:00:00 2014
+++ cui136/ckbox.c	Mon Apr 28 22:00:00 2014
@@ -20,8 +20,7 @@

 	if(evt == CUI_EVT_KEY){
 		if(val == CUI_KEY_ENTER){
-			p->v = !p->v;
-			cui_draw(obj);
+			cui_ckbox_val_set(obj, !p->v);
 			cui_handler_call(obj, CUI_EVT_BUTTON, p->v);
 			return TRUE;
 		}
@@ -48,6 +47,7 @@
 	obj->flags |= CUI_FLG_CAN_FOCUS;
 	cui_bind(obj, CUI_EVT_DRAW | CUI_EVT_KEY, cui_ckbox_hdr, NULL);
 	p->v = v;
+	p->vp = NULL;
 }

 char *
@@ -77,7 +77,16 @@
 {
 	cui_ckbox p = (cui_ckbox)obj;
 	p->v = v;
+	if(p->vp) *p->vp = p->v;
 	cui_draw(obj);
+}
+
+void
+cui_ckbox_vp_set(cui obj, int *vp)
+{
+	cui_ckbox p = (cui_ckbox)obj;
+	p->vp = vp;
+	if(p->vp) cui_ckbox_val_set(obj, *p->vp);
 }

 /* EOF */
diff -urN cui135/ckbox.h cui136/ckbox.h
--- cui135/ckbox.h	Sun Feb  2 23:50:00 2014
+++ cui136/ckbox.h	Mon Apr 28 22:00:00 2014
@@ -7,6 +7,7 @@
 	struct cui_base base;
 	cui lb1, lb2;
 	int v;
+	int *vp;
 } *cui_ckbox;

 cui cui_ckbox_new(cui parent, int x, int y, char *s, int v);
@@ -16,6 +17,7 @@
 void cui_ckbox_str_set(cui obj, char *s);
 int cui_ckbox_val_get(cui obj);
 void cui_ckbox_val_set(cui obj, int v);
+void cui_ckbox_vp_set(cui obj, int *vp);

 #endif

diff -urN cui135/scpanel.c cui136/scpanel.c
--- cui135/scpanel.c	Sun Apr 27 22:00:00 2014
+++ cui136/scpanel.c	Mon Apr 28 22:00:00 2014
@@ -20,11 +20,11 @@
 	p->view = cui_base_new(obj, 1, 1, w-2, h-2);
 	p->sheet = cui_base_new(p->view, 0, 0, w-2, h-2);

-	p->vbar = cui_scbar_new(obj, w-1, 1, 1, h-2, 1, 0);
+	p->vbar = cui_scbar_new(obj, w-1, 1, 1, h-2>1 ? h-2 : 2, 1, 0);
 	cui_hide(p->vbar);
 	cui_bind(p->vbar, CUI_EVT_BUTTON, cui_scpanel_scbar_hdr, obj);

-	p->hbar = cui_scbar_new(obj, 1, h-1, w-2, 1, 1, 0);
+	p->hbar = cui_scbar_new(obj, 1, h-1, w-2>1 ? w-2 : 2, 1, 1, 0);
 	cui_hide(p->hbar);
 	cui_bind(p->hbar, CUI_EVT_BUTTON, cui_scpanel_scbar_hdr, obj);

cui_midi.c の差分です

../midi_src/cui_midi15.patch
--- cui_midi.c-	Sat Apr 26 23:00:00 2014
+++ cui_midi.c	Fri May  2 15:41:57 2014
@@ -12,6 +12,7 @@
 #include "cui/tab.h"
 #include "cui/kbd.h"
 #include "cui/timer.h"
+#include "cui/arg.h"

 FILE *fp = NULL;
 #define BUFN	4096
@@ -184,10 +185,12 @@
 main(int ac, char **av)
 {
 	int init_ret = cui_init(av);
-	cui root = cui_scpanel_new(NULL, 0, 0, 45, 11, "cui_midi");
+	int w = cui_arg_int(av, "-w", 45, NULL);
+	int h = cui_arg_int(av, "-h", 11, NULL);
+	cui root = cui_scpanel_new(NULL, 0, 0, w, h, "cui_midi");
 	cui bs4 = cui_scpanel_sheet_get(root);
 	struct test4 t4;
-	int div = 96;
+	int div = cui_arg_int(av, "-div", 96, NULL);
 	int i;

 	cui_hide(bs4);
@@ -248,9 +251,6 @@
 	cui_handler_call(t4.mn_ch, CUI_EVT_BUTTON, cui_menu_int_get(t4.mn_ch));

 	fp = stdout;
-
-	for(i=1; i<ac; i++) if(strcmp(av[i], "-div") == 0) break;
-	if(i+1 < ac) div = strtol(av[i+1], NULL, 0);

 	set_str("MThd");
 	set_int(4, 6);

prog37 から prog38 の差分です

../midi_src/prog38.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	Sun Apr 27 22:00:00 2014
+++ midi_prog/Makefile	Mon Apr 28 22:00:00 2014
@@ -1,6 +1,6 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog37
+TARG = prog38
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
 OBJS += cui_tone.o
 CFLAGS += -Wall -I..
diff -urN midi_prog-/cui_tone.c midi_prog/cui_tone.c
--- midi_prog-/cui_tone.c	Sun Apr 27 22:00:00 2014
+++ midi_prog/cui_tone.c	Mon Apr 28 22:00:00 2014
@@ -5,6 +5,7 @@
 #include "cui/menu.h"
 #include "cui/num.h"
 #include "cui/handler.h"
+#include "cui/arg.h"
 #include "tone.h"
 #include "ch.h"

@@ -150,7 +151,9 @@
 	char **av = (char **)arg;
 	int init_ret = cui_init(av);

-	cui root = cui_scpanel_new(NULL, 0, 0, 44, 20, "cui_tone");
+	int w = cui_arg_int(av, "-w", 0, NULL);
+	int h = cui_arg_int(av, "-h", 0, NULL);
+	cui root = cui_scpanel_new(NULL, 0, 0, w, h, "cui_tone");
 	cui sheet = cui_scpanel_sheet_get(root);
 	cui ch, note, bs_ch, note_i, bs_vco, bs_filt, bs_env;
 	cui bs_lfo_modu, bs_lfo, bs_env_modu, bs_delay;
@@ -159,7 +162,7 @@
 	char *filt_lst[] = { "OFF", "LPF", "HPF", "BPF", NULL };
 	char *onoff_lst[] = { "OFF", "ON", NULL };

-	ch = cui_menu_int_new(sheet, 0, 0, 2+1, 0, 0, 15);
+	ch = cui_menu_int_new(sheet, 0, 0, -1, 0, 0, 15);
 	cui_wlb_name_new(sheet, "ch ", "ch", NULL);

 	note = cui_num_new(sheet, CUI_CHILD_X2+1, CUI_CHILD_Y, 3+3, 69, 0, 127, 0);
@@ -312,7 +315,8 @@
 	cui_wh_fit(bs_ch);
 	cui_wh_fit(sheet);

-	cui_wh_set(root, sheet->w+2, sheet->h+2);
+	if(w == 0) cui_w_set(root, sheet->w+2);
+	if(h == 0) cui_h_set(root, sheet->h+2);

 	cui_bind(ch, CUI_EVT_BUTTON, ch_note_hdr, sheet);
 	cui_bind(note, CUI_EVT_BUTTON, ch_note_hdr, sheet);

$ cat cui135_136.patch | (cd cui ; make clean ; patch -p1 ; make)
$ cat prog38.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat cui_midi15.patch | patch -p0
$ gcc -Wall -o cui_midi cui_midi.c -Lcui -lcui -lm
$ cui/cui_srv -port 9100

別端末から

$ ./cui_midi -conn 9100 | ./add_delta | midi_prog/prog38 -conn 9100 -h 16 -V0 -q -play

cui_srv に合わせて -h 16 くらいにしてみます

cui_srv の画面

お約束の

prog 50

vol 64

Immediate を Arpeggio に

鍵盤 いくつか ON

repeat チェック ON

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|================= cui_midi ==================             |
||ch 0 |                                     |             |
||                                           |             |
||prog <50 >|  vol (msb) <64 >|  velo <64 >| |             |
||                                           |             |
||6 |C |  <84 >|  <1046.50>|                 |             |
||   |     |       |     |       |     |     |             |
|| | |o| | | |o| | |o| | | | | | | | | | |   |             |
||                                           |             |
||Arpeggio |  <200 >|  (play)  [X] repeat    |             |
|+-------------------------------------------+             |
  :

タブ1 ENTER

note 69 の '>' ENTER

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|====================== cui_tone =======================   |
||ch 0 | note <70 >|                                   -   |
||                                                     =   |
||prog=50  tone_n=2  idx 0|                            |   |
||                                                     |   |
||+-- VCO ----------------------------+                |   |
|||wave1 square|   wave2 square|      |                |   |
|||                tune <10    >| cent|                |   |
|||                mix <0.5 >|        |                |   |
||+-----------------------------------+                |   |
||                                                     |   |
||+-- Filter ------------------------------+           |   |
|||filter1 LPF|         filter2 OFF|       |           |   |
|||freq1 <2000   >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <4    >|  reso2 (Q2) <0    >||           -   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

あとは出来るだけリサイズするなり、スクロールするなりで、

なんとか全ての部品を操作出来るはずです

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|====================== cui_tone =======================   |
||+-- LFO --------------------------------------------+-   |
|||wave sin   |  freq <4      >| Hz  delay <0.3 >| sec||   |
||+---------------------------------------------------+|   |
||                                                     |   |
||+-- ENV modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <0     >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||                                                     |   |
||+-- delay ----------------------+                    |   |
|||ON |  <0.2 >| sec  gain <0.4 >||                    |   |
||+-------------------------------+                    |   |
||                                                     =   |
||chorus OFF|                                          -   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

別端末から起動してる

$ ./cui_midi -conn 9100 | ./add_delta | midi_prog/prog38 -conn 9100 -h 16 -V0 -q -play

を ^C で停止すると

cui_srv 画面は

========================= cui_srv ==========================
|menu|                                                     |
|                                                          |
|                                                          |
  :

画面が残ってる場合は、menu から close を選んで消しておきます

prog38がSMFファイルを読み込んでる時も、cui_tone が動作するか試してみます

$ cat L3007_06.MID | midi_prog/prog38 -conn 9101 -h 16 -V0 -q -play

cui_srv 画面は

========================= cui_srv ==========================
|menu| / 2 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|                                   -   |
||                                                     =   |
||                                                     |   |
  :

ch 1 を選ぶと

========================= cui_srv ==========================
|menu| / 2 |                                               |
|====================== cui_tone =======================   |
||ch 1 | note <69 >|                                   -   |
||                                                     =   |
||prog=35  tone_n=1  idx 0|                            |   |
||                                                     |   |
||+-- VCO ----------------------------+                |   |
|||wave1 saw   |   wave2 square|      |                |   |
|||                tune <0     >| cent|                |   |
|||                mix <0.5 >|        |                |   |
||+-----------------------------------+                |   |
||                                                     |   |
||+-- Filter ------------------------------+           |   |
|||filter1 LPF|         filter2 OFF|       |           |   |
|||freq1 <1000   >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <1.5  >|  reso2 (Q2) <0    >||           -   |
|+-----------------------------------------------------+   |

ch 9 で note 37 にすると

========================= cui_srv ==========================
|menu| / 2 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <37 >|                                   -   |
||                                                     =   |
||prog=0   tone_n=1  idx 0|                            |   |
||                                                     |   |
||+-- VCO ----------------------------+                |   |
|||wave1 noise |   wave2 square|      |                |   |
|||                tune <0     >| cent|                |   |
|||                mix <0.2 >|        |                |   |
||+-----------------------------------+                |   |
||                                                     |   |
||+-- Filter ------------------------------+           |   |
|||filter1 LPF|         filter2 OFF|       |           |   |
|||freq1 <1700   >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <3    >|  reso2 (Q2) <0    >||           -   |
|+-----------------------------------------------------+   |

ドラムのサイドスティックのパラメータが表示されます

残念ながら波形の出力がそうとう間に合ってないようで、 音がブチブチに切れて聞こえます... ^Cで停止して

$ cat L3007_06.MID | midi_prog/prog38 -conn 9100 -h 16 -r 8000 -c 1 -b 8 -u -V0 -q -play

これでかなり音質が落ちますが、 ブチブチ切れる現象は、かなりましになりました

VCOでリングモジュレーションの設定が抜けてました。

追加します

他、若干の修正です。

cui_tone の画面が少々大きすぎるので、 つめつめに配置してみました。

あとON/OFFだけのものはチェックボックスを使ってみました

prog38 から prog39 の差分です

prog39.patch
diff -urN midi_prog-/Makefile midi_prog/Makefile
--- midi_prog-/Makefile	Mon Apr 28 22:00:00 2014
+++ midi_prog/Makefile	Mon Apr 28 22:30:00 2014
@@ -1,6 +1,6 @@
 CC = gcc
 LIB = -lm -lpthread -L../cui -lcui
-TARG = prog38
+TARG = prog39
 OBJS = main.o vcf.o ch.o delay.o stat.o note.o env.o tone.o filter.o lfo.o modu.o vco.o wave.o out.o rd.o util.o
 OBJS += cui_tone.o
 CFLAGS += -Wall -I..
diff -urN midi_prog-/cui_tone.c midi_prog/cui_tone.c
--- midi_prog-/cui_tone.c	Mon Apr 28 22:00:00 2014
+++ midi_prog/cui_tone.c	Mon Apr 28 22:30:00 2014
@@ -4,6 +4,7 @@
 #include "cui/button.h"
 #include "cui/menu.h"
 #include "cui/num.h"
+#include "cui/ckbox.h"
 #include "cui/handler.h"
 #include "cui/arg.h"
 #include "tone.h"
@@ -27,84 +28,48 @@
 	struct tone_compo_rec *tcr = tone_compo_get(ch_v, note_v, &n);
 	struct tone_rec *tr;

-	cui wave1 = cui_name_child(sheet, "wave1");
-	cui wave2 = cui_name_child(sheet, "wave2");
-	cui tune = cui_name_child(sheet, "tune");
-	cui mix = cui_name_child(sheet, "mix");
-
-	cui filt1 = cui_name_child(sheet, "filt1");
-	cui filt2 = cui_name_child(sheet, "filt2");
-	cui freq1 = cui_name_child(sheet, "freq1");
-	cui freq2 = cui_name_child(sheet, "freq2");
-	cui Q1 = cui_name_child(sheet, "Q1");
-	cui Q2 = cui_name_child(sheet, "Q2");
-
-	cui attack = cui_name_child(sheet, "attack");
-	cui decay = cui_name_child(sheet, "decay");
-	cui sustain = cui_name_child(sheet, "sustain");
-	cui release = cui_name_child(sheet, "release");
-
-	cui level = cui_name_child(sheet, "level");
-
-	cui lfo_mp1 = cui_name_child(sheet, "lfo_mp1");
-	cui lfo_mp2 = cui_name_child(sheet, "lfo_mp2");
-	cui lfo_mf1 = cui_name_child(sheet, "lfo_mf1");
-	cui lfo_mf2 = cui_name_child(sheet, "lfo_mf2");
-
-	cui lfo_wave = cui_name_child(sheet, "lfo_wave");
-	cui lfo_freq = cui_name_child(sheet, "lfo_freq");
-	cui lfo_delay = cui_name_child(sheet, "lfo_delay");
-
-	cui env_mp1 = cui_name_child(sheet, "env_mp1");
-	cui env_mp2 = cui_name_child(sheet, "env_mp2");
-	cui env_mf1 = cui_name_child(sheet, "env_mf1");
-	cui env_mf2 = cui_name_child(sheet, "env_mf2");
-
-	cui delay = cui_name_child(sheet, "delay");
-	cui delay_sec = cui_name_child(sheet, "delay_sec");
-	cui delay_gain = cui_name_child(sheet, "delay_gain");
-	cui chorus  = cui_name_child(sheet, "chorus");
-
 	i = cui_menu_int_get(tone_i);
 	tr = tcr[i].tone;

-	cui_menu_popup_vp_set(wave1, &tr->vco.wave1);
-	cui_menu_popup_vp_set(wave2, &tr->vco.wave2);
-	cui_num_vp_set(tune, &tr->vco.tune);
-	cui_num_dbl_vp_set(mix, &tr->vco.mix);
-
-	cui_menu_popup_vp_set(filt1, &tr->fl1.type);
-	cui_num_dbl_vp_set(freq1, &tr->fl1.freq);
-	cui_num_dbl_vp_set(Q1, &tr->fl1.Q);
-	cui_menu_popup_vp_set(filt2, &tr->fl2.type);
-	cui_num_dbl_vp_set(freq2, &tr->fl2.freq);
-	cui_num_dbl_vp_set(Q2, &tr->fl2.Q);
-
-	cui_num_dbl_vp_set(attack, &tr->env.attack);
-	cui_num_dbl_vp_set(decay, &tr->env.decay);
-	cui_num_dbl_vp_set(sustain, &tr->env.sustain);
-	cui_num_dbl_vp_set(release, &tr->env.release);
-
-	cui_num_dbl_vp_set(level, &tr->level);
-
-	cui_num_vp_set(lfo_mp1, &tr->lfo_modu.pitch1);
-	cui_num_vp_set(lfo_mp2, &tr->lfo_modu.pitch2);
-	cui_num_vp_set(lfo_mf1, &tr->lfo_modu.filter1);
-	cui_num_vp_set(lfo_mf2, &tr->lfo_modu.filter2);
-
-	cui_menu_popup_vp_set(lfo_wave, &tr->lfo.wave);
-	cui_num_dbl_vp_set(lfo_freq, &tr->lfo.freq);
-	cui_num_dbl_vp_set(lfo_delay, &tr->lfo.delay);
-
-	cui_num_vp_set(env_mp1, &tr->env_modu.pitch1);
-	cui_num_vp_set(env_mp2, &tr->env_modu.pitch2);
-	cui_num_vp_set(env_mf1, &tr->env_modu.filter1);
-	cui_num_vp_set(env_mf2, &tr->env_modu.filter2);
-
-	cui_menu_popup_vp_set(delay, &tr->delay.onoff);
-	cui_num_dbl_vp_set(delay_sec, &tr->delay.sec);
-	cui_num_dbl_vp_set(delay_gain, &tr->delay.gain);
-	cui_menu_popup_vp_set(chorus, &tr->chorus);
+	cui_menu_popup_vp_set(cui_name_child(sheet, "wave1"), &tr->vco.wave1);
+	cui_menu_popup_vp_set(cui_name_child(sheet, "wave2"), &tr->vco.wave2);
+	cui_num_vp_set(cui_name_child(sheet, "tune"), &tr->vco.tune);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "mix"), &tr->vco.mix);
+	cui_ckbox_vp_set(cui_name_child(sheet, "ring"), &tr->vco.ring);
+
+	cui_menu_popup_vp_set(cui_name_child(sheet, "filt1"), &tr->fl1.type);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "freq1"), &tr->fl1.freq);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "Q1"), &tr->fl1.Q);
+	cui_menu_popup_vp_set(cui_name_child(sheet, "filt2"), &tr->fl2.type);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "freq2"), &tr->fl2.freq);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "Q2"), &tr->fl2.Q);
+
+	cui_num_dbl_vp_set(cui_name_child(sheet, "attack"), &tr->env.attack);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "decay"), &tr->env.decay);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "sustain"), &tr->env.sustain);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "release"), &tr->env.release);
+
+	cui_num_dbl_vp_set(cui_name_child(sheet, "level"), &tr->level);
+
+	cui_num_vp_set(cui_name_child(sheet, "lfo_mp1"), &tr->lfo_modu.pitch1);
+	cui_num_vp_set(cui_name_child(sheet, "lfo_mp2"), &tr->lfo_modu.pitch2);
+	cui_num_vp_set(cui_name_child(sheet, "lfo_mf1"), &tr->lfo_modu.filter1);
+	cui_num_vp_set(cui_name_child(sheet, "lfo_mf2"), &tr->lfo_modu.filter2);
+
+	cui_menu_popup_vp_set(cui_name_child(sheet, "lfo_wave"), &tr->lfo.wave);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "lfo_freq"), &tr->lfo.freq);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "lfo_delay"), &tr->lfo.delay);
+
+	cui_num_vp_set(cui_name_child(sheet, "env_mp1"), &tr->env_modu.pitch1);
+	cui_num_vp_set(cui_name_child(sheet, "env_mp2"), &tr->env_modu.pitch2);
+	cui_num_vp_set(cui_name_child(sheet, "env_mf1"), &tr->env_modu.filter1);
+	cui_num_vp_set(cui_name_child(sheet, "env_mf2"), &tr->env_modu.filter2);
+
+	cui_ckbox_vp_set(cui_name_child(sheet, "delay"), &tr->delay.onoff);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "delay_sec"), &tr->delay.sec);
+	cui_num_dbl_vp_set(cui_name_child(sheet, "delay_gain"), &tr->delay.gain);
+
+	cui_ckbox_vp_set(cui_name_child(sheet, "chorus"), &tr->chorus);

 	return TRUE;
 }
@@ -119,23 +84,20 @@
 	int ch_v = cui_menu_int_get(ch);
 	cui note = cui_name_child(sheet, "note");
 	int note_v = cui_num_get(note);
-	cui bs_ch = cui_name_child(sheet, "bs_ch");
-	cui prog = cui_name_child(sheet, "prog");
 	static char prog_buf[16];
-	cui tone_n = cui_name_child(sheet, "tone_n");
 	static char tone_n_buf[16];
 	cui tone_i = cui_name_child(sheet, "tone_i");
 	int i, n;
 	struct tone_compo_rec *tcr = tone_compo_get(ch_v, note_v, &n);

-	cui_show_hide(bs_ch, tcr != NULL);
+	cui_show_hide(cui_name_child(sheet, "bs_ch"), tcr != NULL);
 	if(tcr == NULL) return TRUE;

 	sprintf(prog_buf, "prog=%d", CH_PROG(ch_v));
-	cui_label_str_set(prog, prog_buf);
+	cui_label_str_set(cui_name_child(sheet, "prog"), prog_buf);

 	sprintf(tone_n_buf, "tone_n=%d", n);
-	cui_label_str_set(tone_n, tone_n_buf);
+	cui_label_str_set(cui_name_child(sheet, "tone_n"), tone_n_buf);

 	cui_menu_int_min_max_set(tone_i, 0, n-1);

@@ -160,7 +122,6 @@

 	char *wave_lst[] = { "sin", "saw", "square", "noise", NULL };
 	char *filt_lst[] = { "OFF", "LPF", "HPF", "BPF", NULL };
-	char *onoff_lst[] = { "OFF", "ON", NULL };

 	ch = cui_menu_int_new(sheet, 0, 0, -1, 0, 0, 15);
 	cui_wlb_name_new(sheet, "ch ", "ch", NULL);
@@ -168,7 +129,7 @@
 	note = cui_num_new(sheet, CUI_CHILD_X2+1, CUI_CHILD_Y, 3+3, 69, 0, 127, 0);
 	cui_wlb_name_new(sheet, "note ", "note", NULL);

-	bs_ch = cui_base_new(sheet, 0, CUI_CHILD_Y2+1, 0, 0);
+	bs_ch = cui_base_new(sheet, 0, CUI_CHILD_Y2, 0, 0);
 	bs_ch->name = "bs_ch";

 	cui_label_new(bs_ch, 0, 0, "prog=   ")->name = "prog";
@@ -179,7 +140,7 @@

 	/* vco */

-	bs_vco = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " VCO ");
+	bs_vco = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " VCO ");

 	cui_menu_popup_new(bs_vco, 0, 0, -1, wave_lst, 0);
 	cui_wlb_name_new(bs_vco, "wave1 ", "wave1", NULL);
@@ -193,11 +154,13 @@
 	cui_num_dbl_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, 4+3, 0, 0, 1, 0.01, 0);
 	cui_wlb_name_new(bs_vco, "mix ", "mix", NULL);

+	cui_ckbox_new(bs_vco, CUI_CHILD_X, CUI_CHILD_Y2, "ring", FALSE)->name = "ring";
+
 	cui_wh_fit(bs_vco);

 	/* filt */

-	bs_filt = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " Filter ");
+	bs_filt = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " Filter ");

 	cui_menu_popup_new(bs_filt, 0, 0, -1, filt_lst, 0);
 	cui_wlb_name_new(bs_filt, "filter1 ", "filt1", NULL);
@@ -223,7 +186,7 @@

 	/* env */

-	bs_env = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " ENV ");
+	bs_env = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " ENV ");

 	cui_num_dbl_new(bs_env, 0, 0, 5+3, 0, 0, 10, 0.01, 0);
 	cui_wlb_name_new(bs_env, "attack  ", "attack", " sec");
@@ -239,12 +202,12 @@

 	cui_wh_fit(bs_env);

-	cui_num_dbl_new(bs_ch, 0, CUI_CHILD_Y2+1, 5+3, 0, 0, 10, 0.01, 0);
+	cui_num_dbl_new(bs_ch, 0, CUI_CHILD_Y2, 5+3, 0, 0, 10, 0.01, 0);
 	cui_wlb_name_new(bs_ch, "level ", "level", NULL);

 	/* lfo modu */

-	bs_lfo_modu = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " LFO modu ");
+	bs_lfo_modu = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " LFO modu ");

 	cui_num_new(bs_lfo_modu, 0, 0, 6+3, 0, -24000, 24000, 0);
 	cui_wlb_name_new(bs_lfo_modu, "pitch1  ", "lfo_mp1", " cent");
@@ -262,7 +225,7 @@

 	/* lfo */

-	bs_lfo = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " LFO ");
+	bs_lfo = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " LFO ");

  	cui_menu_popup_new(bs_lfo, 0, 0, -1, wave_lst, 0);
 	cui_wlb_name_new(bs_lfo, "wave ", "lfo_wave", NULL);
@@ -277,7 +240,7 @@

 	/* env modu */

-	bs_env_modu = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " ENV modu ");
+	bs_env_modu = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " ENV modu ");

 	cui_num_new(bs_env_modu, 0, 0, 6+3, 0, -24000, 24000, 0);
 	cui_wlb_name_new(bs_env_modu, "pitch1  ", "env_mp1", " cent");
@@ -295,10 +258,9 @@

 	/* delay */

-	bs_delay = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2+1, 0, 0, " delay ");
+	bs_delay = cui_title_panel_new_sheet(bs_ch, 0, CUI_CHILD_Y2, 0, 0, " delay ");

- 	cui_menu_popup_new(bs_delay, 0, 0, -1, onoff_lst, 0);
-	cui_wlb_name_new(bs_delay, NULL, "delay", NULL);
+	cui_ckbox_new(bs_delay, 0, 0, "ON", FALSE)->name = "delay";

 	cui_num_dbl_new(bs_delay, CUI_CHILD_X2+2, CUI_CHILD_Y, 4+3, 0, 0, 10, 0.1, 0);
 	cui_wlb_name_new(bs_delay, NULL, "delay_sec", " sec");
@@ -308,8 +270,7 @@

 	cui_wh_fit(bs_delay);

-	cui_menu_popup_new(bs_ch, 0, CUI_CHILD_Y2+1, -1, onoff_lst, 0);
-	cui_wlb_name_new(bs_ch, "chorus ", "chorus", NULL);
+	cui_ckbox_new(bs_ch, 0, CUI_CHILD_Y2, "chorus", FALSE)->name = "chorus";

 	cui_hide(bs_ch);
 	cui_wh_fit(bs_ch);

$ cat prog39.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ ./cui_midi -conn 9100 | ./add_delta | midi_prog/prog39 -conn 9100 -V0 -q -play

cui_srv 画面

タブ1 選択

ch 9

note 46

全体のバランスはこんな感じです

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|====================== cui_tone =======================   |
||ch 9 | note <46 >|                                   |   |
||prog=0   tone_n=1  idx 0|                            |   |
||+-- VCO ----------------------------+                |   |
|||wave1 square|   wave2 square|      |                |   |
|||                tune <700   >| cent|                |   |
|||                mix <0.9 >|        |                |   |
|||                [X] ring           |                |   |
||+-----------------------------------+                |   |
||+-- Filter ------------------------------+           |   |
|||filter1 LPF|         filter2 OFF|       |           |   |
|||freq1 <16000  >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <2    >|  reso2 (Q2) <0    >||           |   |
||+----------------------------------------+           |   |
||+-- ENV ----------------+                            |   |
|||attack  <0    >| sec   |                            |   |
|||decay   <0    >| sec   |                            |   |
|||sustain <1    >| (0..1)|                            |   |
|||release <0.35 >| sec   |                            |   |
||+-----------------------+                            |   |
||level <0.6  >|                                       |   |
||+-- LFO modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <0     >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||+-- LFO --------------------------------------------+|   |
|||wave sin   |  freq <0      >| Hz  delay <0   >| sec||   |
||+---------------------------------------------------+|   |
||+-- ENV modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <0     >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||+-- delay ------------------------+                  |   |
|||[ ] ON  <0   >| sec  gain <0   >||                  |   |
||+---------------------------------+                  |   |
||[ ] chorus                                           |   |
|+-----------------------------------------------------+   |
+----------------------------------------------------------+


工事中