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


  |  X  | X  X  |     |       |
  | | | | | | | | | | | | | | | 



はじめに


音を鳴らしてみる

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データ形式

鳴らしてみる音

プログラム prog_sin.c

解説

コンパイル

$ 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 なら、その周波数は

プログラム prog_chord.c

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

$ 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

解説

$ 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ビットが|2バイト目がデータ長                            |
|               |        |0            |(3バイト目以降のバイト数で終端の 0xf7 分も含む)|
|               |        |             |イベント末尾のバイトは 0xf7 で終端             |
|               |        +-------------+-----------------------------------------------+
|               |        |1バイト目の  |メタイベント                                   |
|               |        |下位4ビットが|2バイト目がイベントタイプ                      |
|               |        |0xf          |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バイト目の   |システム・エクスクルーシブ・メッセージ         |
|               |        |n下位4ビットが|2バイト目がデータ長                            |
|               |        |0             |(3バイト目以降のバイト数で終端の 0xf7 分も含む)|
|               |        |              |イベント末尾のバイトは 0xf7 で終端             |
|               +--------+--------------+-----------------------------------------------+
|               |2バイト |1バイト目の   |MIDIタイム・コード                             |
|               |        |下位4ビットが |2バイト目は0から127の値                        |
|               |        |1             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |3バイト |1バイト目の   |ソング・ポジション                             |
|               |        |下位4ビットが |2バイト目はパラメータの下位7ビット             |
|               |        |2             |3バイト目はパラメータの上位7ビット             |
|               +--------+--------------+-----------------------------------------------+
|               |2バイト |1バイト目の   |ソング番号                                     |
|               |        |下位4ビットが |2バイト目はソング番号で0から127の値            |
|               |        |3             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |未定義  |1バイト目の   |未定義                                         |
|               |        |下位4ビットが |                                               |
|               |        |4             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |未定義  |1バイト目の   |未定義                                         |
|               |        |下位4ビットが |                                               |
|               |        |5             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |1バイト |1バイト目の   |チューン・リクエスト                           |
|               |        |下位4ビットが |                                               |
|               |        |6             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |...     |1バイト目の   |システム・エクスクルーシブ・メッセージの終端用 |
|               |        |下位4ビットが |                                               |
|               |        |7             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |1バイト |1バイト目の   |MIDIクロック                                   |
|               |        |下位4ビットが |                                               |
|               |        |8             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |未定義  |1バイト目の   |未定義                                         |
|               |        |下位4ビットが |                                               |
|               |        |9             |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |1バイト |1バイト目の   |シーケンス開始                                 |
|               |        |下位4ビットが |                                               |
|               |        |0xa           |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |1バイト |1バイト目の   |シーケンス再開                                 |
|               |        |下位4ビットが |                                               |
|               |        |0xb           |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |1バイト |1バイト目の   |シーケンス停止                                 |
|               |        |下位4ビットが |                                               |
|               |        |0xc           |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |未定義  |1バイト目の   |未定義                                         |
|               |        |下位4ビットが |                                               |
|               |        |0xd           |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |1バイト |1バイト目の   |アクティブ・センシング                         |
|               |        |下位4ビットが |                                               |
|               |        |0xe           |                                               |
|               +--------+--------------+-----------------------------------------------+
|               |可変    |1バイト目の   |メタイベント                                   |
|               |        |下位4ビットが |2バイト目がイベントタイプ                      |
|               |        |0xf           |3バイト目がデータ長                            |
|               |        |              |(4バイト目以降に続くバイト数)                  |
+---------------+--------+--------------+-----------------------------------------------+

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

プログラム prog_onoff_sin3.c

解説


音の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

解説

$ 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コマンド)のオプション指定では

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

と言うことは、プログラムからどちらの形式で出力しても、 そのデータを扱う 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

プログラム prog_sin16_be.c

$ 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

解説

$ 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

解説

$ 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 を実行すると

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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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 を使って、鍵盤オン・イベントの中身を覗いてみましょう

$ 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 を使ってみます

$ 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

解説

$ 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

解説

$ 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 で覗いてみます

$ 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つに分けると

うーむ。表現できる値は偶数個あるので、丁度中央の値は無いですね

逆に、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

うー。丁度中央の値には設定できませんね。
確かにそうなのですが、右最大の設定をあきらめれば、どうでしょうか

と考えてみます。
14ビットの値は、0 から 16383 まで。
なので、右最大の 16384 は表現できずに設定できません。

しかしこの考えにすれば、先の1行目は

		pan = 14ビットの値 / 16384.0

となり、中央の値は確かに 0.5 * 16384.0 = 8192

まぁ、右最大で 1/16384 程度ズレますが、 中央の設定が、中央に定まらないよりは、いいのかもしれません

プログラム prog2.c

解説

$ 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 で、 該当の処理をしてる箇所は、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
メタ・イベントのタイプ 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

	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

解説

$ 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

解説

$ 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 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

解説

$ 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 の 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

解説

$ 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

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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 の 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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

解説

$ 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           |        |
|         +-------------------+        |
|         |string.h           |        |
|         +-------------------+        |
|         |math.h             |        |
|---------+-------------------+        +
|マクロ   |MSG                |        |
|         +-------------------+        |
|         |ERR                |        |
+---------+-------------------+--------+
|変数     |bk_buf             |rd.c    |
|---------+-------------------+        +
|関数     |rd                 |        |
|         +-------------------+        |
|         |bk                 |        |
|         +-------------------+        |
|         |rd_str             |        |
|         +-------------------+        |
|         |rd_str_chk         |        |
|         +-------------------+        |
|         |rd_int             |        |
|         +-------------------+        |
|         |rd_delta           |        |
+---------+-------------------+--------+
|関数     |opt_idx            |util.c  |
|         +-------------------+        |
|         |opt_str            |        |
|         +-------------------+        |
|         |opt_int            |        |
+---------+-------------------+--------+
|構造体   |out_rec            |out.h   |
+---------+-------------------+--------+
|関数     |sox_version        |out.c   |
|         +-------------------+        |
|         |sox_bit_len_fmt    |        |
|         +-------------------+        |
|         |out_init           |        |
|         +-------------------+        |
|         |out_do             |        |
+---------+-------------------+--------+
|マクロ   |OFF                |util.h  |
|         +-------------------+        |
|         |ON                 |        |
+---------+-------------------+--------+
|マクロ   |LPF                |filter.h|
|         +-------------------+        |
|         |HPF                |        |
|         +-------------------+        |
|         |BPF                |        |
|---------+-------------------+        +
|構造体   |filter_rec         |        |
|         +-------------------+        |
|         |filter_stat_rec    |        |
+---------+-------------------+--------+
|関数     |filter_update      |filter.c|
|         +-------------------+        |
|         |filter_init        |        |
|         +-------------------+        |
|         |filter_out         |        |
+---------+-------------------+--------+
|マクロ   |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      |        |
+---------+-------------------+--------+
|構造体   |note_rec           |note.h  |
+---------+-------------------+--------+
|変数     |note_buf           |note.c  |
+---------+-------------------+--------+
|関数     |note_buf_is_free   |note.c  |
|         +-------------------+        |
|         |note_buf_free      |        |
|         +-------------------+        |
|         |note_buf_init      |        |
|         +-------------------+        |
|         |note_buf_search    |        |
+---------+-------------------+--------+
|関数     |header             |main.c  |
+---------+-------------------+--------+
|関数     |note_to_freq       |note.c  |
+---------+-------------------+--------+
|マクロ   |WAVE_SIN           |wave.h  |
|         +-------------------+        |
|         |WAVE_SAW           |        |
|         +-------------------+        |
|         |WAVE_SQUARE        |        |
|         +-------------------+        |
|         |WAVE_NOISE         |        |
+---------+-------------------+--------+
|関数     |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_compo_rec     |tone.h  |
+---------+-------------------+--------+
|マクロ   |PROG_DRUM          |tone.c  |
|---------+-------------------+        +
|変数     |tones_lst          |        |
+---------+-------------------+--------+
|関数     |stat_lst_free      |stat.c  |
|         +-------------------+        |
|         |stat_lst_alloc_init|        |
+---------+-------------------+--------+
|マクロ   |MIDI_CH_N          |ch.h    |
+---------+-------------------+--------+
|構造体   |ch_rec             |ch.h    |
+---------+-------------------+--------+
|変数     |ch_inf             |ch.c    |
+---------+-------------------+--------+
|マクロ   |CH_PROG            |tone.c  |
|---------+-------------------+        +
|関数     |tone_compo_get     |        |
+---------+-------------------+--------+
|関数     |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_out          |        |
|         +-------------------+        |
|         |chorus_out         |        |
+---------+-------------------+--------+
|関数     |tone.out           |tone.c  |
+---------+-------------------+--------+
|関数     |note_out           |note.c  |
+---------+-------------------+--------+
|関数     |data_out           |main.c  |
+---------+-------------------+--------+
|関数     |note_onoff         |note.c  |
+---------+-------------------+--------+
|関数     |msb_set            |ch.c    |
|         +-------------------+        |
|         |lsb_set            |        |
+---------+-------------------+--------+
|関数     |bend_note_update   |main.c  |
|         +-------------------+        |
|         |main               |        |
+---------+-------------------+--------+

構造体の定義と変数宣言を同時にしてる箇所が、何箇所かあります

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

prog27 の midi_prog.tgz と prog28.patch からは、 次の手順でビルドします

$ tar xzf midi_prog.tgz
$ cd midi_prog
$ patch -p1 < ../prog28.patch
$ make


エイリアスノイズ

全然外してるかも知れないのですが、 エイリアスノイズとやらの対策にトライしてみます

そもそもエイリアスノイズとは、そしてその対策とは?

素晴らしい説明のページ がありました 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で考えると

      |
   1 -+   +----+
      |   |    |
      |   |    |
      |   |    |
      |   |    |
   0 -+---+----+----+--
      |        |    |
      |        |    |
      |        |    |
      |        |    |
  -1 -+        +----+
      |
      +---+----+----+---> x
          |    |    |
          0   0.5   1

x=0の位置からの積分を考えると
x=0.5までは単調増加で、x=0.5のときは面積からして0.5
x=0.5を超えると単調減少で、x=1までいくと0

      |
 0.5 -+        .
      |       / \
      |      /   \
      |     /     \
      |    /       \
   0 -+---+----+----+---> x
          |    |    |
          0   0.5   1

cycle の値 0〜1 までとして
cycle < 0.5 ? cycle : 1 - cycle
になります

ノコギリも同様に考えると

      |
   1 -+        .
      |       /|
      |      / |
      |     /  |
      |    /   |
   0 -+---+----+----+--
      |        |   /
      |        |  /
      |        | /
      |        |/
  -1 -+        .
      |
      +---+----+----+---> x
          |    |    |
          0   0.5   1

x=0の位置からの積分を考えると
x=0.5までは2次関数で増加して、x=0.5のときは面積からして0.25
x=0.5を超えると2次関数で減少で、x=1までいくと0

      |
0.25 -+        .
      |       | |
      |       . .
      |      /   \
      |    _       _
   0 -+---+----+----+---> x
          |    |    |
          0   0.5   1

(むー。図に無理がある...)

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

この程度の演算の追加なら、さほど負荷も変わらないように思います。
そして、果たして、違いが判るほどの効果があるものなのか...?

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

前段のプログラムで、このクロック・イベントを定期的に出力すればよさそうです。
イベントの処理は何もしないで、その時刻までの波形を生成させたいだけなので、四分音符あたり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

まず本体をビルド

$ 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

改めて

$ 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

上流側の処理も test_play と同等になるように test_play2.c として作ってみます

test_play2.c

$ 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

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
まず 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

========== cui_midi ==========
|                            |
|  (Quit)                    |
|                            |
|  0 | 0 |  (Add)  (Out)     |
|                            |
|                            |
|                            |
|                            |
+----------------------------+

この状態で、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 を引っ張ってきます

$ (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 を更新してビルドし、実行します

$ 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 cui103_104.patch を引っ張ってきます

$ (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 を更新してビルドし、実行します

$ 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 を更新してビルドし、実行します

$ 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 の方に更新が入ってるので、先に反映しておきます。
次のパッチファイル を引っ張ってきます。

$ (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 を更新してビルドし、実行します

$ 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 を更新してビルドし、実行します

$ 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

$ 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

$ patch -p0 < 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

$ cat cui_kbd.patch | (cd cui ; make clean ; patch -p1 ; make)

そろそろ 4th のタブだけあれば大丈夫そうなので、 最初の頃の画面は削除しておきます

で、従来通りか確認です

cui_midi.c の差分です cui_midi9.patch

随分すっきりしました

$ patch -p0 < 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

$ patch -p0 < 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側の更新分の反映から

$ 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

つづいて midi_prog に簡単なCUIと pthread の処理を追加してみます

prog31 から prog32 の差分です prog32.patch

$ cat prog32.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

$ patch -p0 < 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

midi_prog の cui_tone から、 (update)ボタンは削除して、 chのポップアップメニューを追加しました

prog32 から prog33 の差分です prog33.patch


ではVCOから

実験成功という事で、さっそくこの方式でVCOの画面から進めてみます

まずはCUI側の更新から

$ cat cui129_130.patch | (cd cui ; make clean ; patch -p1 ; make)

とりあえずほぼそのままで、CUI14で追加された仕組みを使ってみます。 なので見た目は、ほぼそのままです

pthread で実行する、CUI画面用のソースファイル名を pth.c から cui_tone.c に変更しておきます

prog33 から prog34 の差分です prog34.patch

$ cat prog34.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

さらにCUI側の更新をおっかけます

prog34 から prog35 の差分です prog35.patch

テスト用として midi_prog/tone.c のプログラム番号50の 音色の数を2つに変更してます

cui_midi.c の差分です cui_midi13.patch

まず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 の '>' にでもフォーカスして 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の更新からです

cui_midi.c の差分です cui_midi14.patch

prog35 から prog36 の差分です prog36.patch

$ 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 の '>' 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の更新からです

prog36 から prog37 の差分です prog37.patch

$ 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 高さ

として指定できるようにしました

cui_midi.c の差分です cui_midi15.patch

prog37 から prog38 の差分です prog38.patch

$ 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

$ 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                                           |   |
|+-----------------------------------------------------+   |
+----------------------------------------------------------+


音色の構造体

一年以上の間があいてしまいましたが、CUI14の方を久々に更新しました。 なので、こちらのMIDIの方もぼちぼちやってみます

さて、CUI14の更新で若干操作性が向上したので、 そろそろターゲットのSMF (はじめに) 以外も視野にいれつつ、音色のパラメータでも調整しようかとおもいつつ

となるとパラメータをファイルにsave/load出来るようにしたいなぁ...と

どーれどれと久々にコードを見てみると

tone.c

static struct tone_rec 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, },
  :
	},{	/* 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, }, 
  :
static 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, 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[2], -1, 1.0 }, /* test */
  :

MIDIチャンネルに設定するプログラム番号には、 tone_compo_rec構造体を複数個指定できて、 さらにtone_compo_rec構造体では、tone_rec構造体のアドレスを指定...

この「アドレス」というのがsave/loadではよろしくなさそうです。
saveでアドレスを保存しても、loadしたとき同じアドレスに配置される保証などありません。
tone_rec構造体に文字列で名前をつけて、tone_compo_rec構造体ではアドレスではなく、文字列の名前で指定するようにしてみます。
起動時に名前をサーチしてアドレスも設定するようにしておけば、以降は従来のままの処理が使えます

そうした上でsave/loadの仕組みを...となるのですが、とりあえずここまで。
名前を導入しても、従来通り動作するか確かめてから先に進みましょう

prog39 から prog40 の差分です prog40.patch

$ cat L3007_06.MID | midi_prog/prog40 -srv 9102 -play

とくに問題なく、以前の音色で鳴ってるようです

名前の導入で、音色設定の可読性が向上しました。 save/loadの前に、ちょっと他の曲のSMF用の音色データを追加して試してみます

「東風」L5003_02.MID

同じく、20年くらい前に前の会社の友人に貰ったローランドのSMFです

まず、以前ここでやったように SMFで使われてるプログラム番号を確認し、GMの音色名をみてみます

$ cat L5003_02.MID | ./prog_onoff9 | grep prog
sec=0.428 prog num ch=0 v=26
sec=0.450 prog num ch=1 v=34
sec=0.472 prog num ch=2 v=126
sec=0.494 prog num ch=3 v=49
sec=0.516 prog num ch=4 v=49
sec=0.538 prog num ch=5 v=62
sec=0.559 prog num ch=6 v=62
sec=0.581 prog num ch=7 v=9
sec=0.603 prog num ch=8 v=29
sec=0.625 prog num ch=9 v=0
sec=0.647 prog num ch=10 v=80
sec=0.669 prog num ch=11 v=74
sec=0.691 prog num ch=12 v=81
sec=64.956 prog num ch=2 v=80
sec=106.862 prog num ch=8 v=81
sec=215.341 prog num ch=2 v=126
sec=237.089 prog num ch=8 v=29
sec=278.138 prog num ch=2 v=80
sec=350.488 prog num ch=2 v=126
sec=350.666 prog num ch=4 v=125
$ 
26	Acoustic Guitar (steel)	アコースティックギター(スチール弦)
34	Electric Bass (finger)	エレクトリックベース(フィンガー)
126	Helicopter	ヘリコプター
49	アンサンブル	String Ensemble 1	ストリングスアンサンブル1
62	Brass Section	ブラスセクション
9	Celesta	チェレスタ
29	Electric Guitar (muted)	エレクトリックギター(ミュート)
80	Ocarina	オカリナ
74	Flute	フルート
81	リード	Lead 1 (square)	リード1(矩形波)
125	Telephone Ring	電話

色々ありますが、電話の音って...
まぁ結局YMOなので、ほぼ当時のシンセサイザーなのでしょうけど

続いてチャンネル9のドラムのノート番号

$ cat L5003_02.MID | ./prog_onoff9 | grep 'on.*ch=9' | sed -e 's/.*\(note=[0-9]*\) .*/\1/' | sort | uniq
note=36
note=40
note=42
note=44
note=46
note=57
36	Bass Drum 1	バスドラム1
40	Electric Snare	エレクトリックスネア
42	Closed Hi Hat	クローズドハイハット
44	Pedal Hi-Hat	ペダルハイハット
46	Open Hi-Hat	オープンハイハット
57	Crash Cymbal 2	クラッシュシンバル2

音色のパラメータはThe End of Asiaで使ってるままで、 東風で使ってるプログラム番号にも割り振ってみます

prog40 から prog41 の差分です prog41.patch

$ cat L5003_02.MID | ./prog41 -srv 9103 -play

とりあえず鳴ってるレベルといったところで、バランスもバラバラ

こうなると、今度はチャンネル毎に音量の調整やミュートできるようにしたいなぁ...
などと考えだして、パラメータのsave/loadはどんどん先送りされるのであった


調整用ボリューム

チャンネルごとの音量調整を追加します

チャンネル毎の音量は、チャンネル毎に持つ情報の構造体に既に含まれてます

ch.h

struct ch_rec{
	int vol;
	int pan;
	int bend;
	int rpn;
	int bend_range;
	int prog;
};

先頭のメンバvolがそれですが、ここでは送られてきたMIDIデータのチャンネル毎の音量の情報を保持してるので、 これとは別に、ちょっと調整したい時用として追加してみます。
チャンネル毎の情報には違いないので、安易に上記の構造体に adj_vol として追加してしまいます

次に、起動時のオプションとして、色々指定する方法を考えみました。

"-vol"に続く文字列とってきて、ループで回しながらsscanfでなんとかなりました。

ch.c ch_init()

	if((s = opt_str("-vol", ac, av, NULL)) != NULL){
		while(*s && sscanf(s, "%[^:]:%s", s_ch, buf) == 2){
			v = strtod(buf, &s);
			if(strcmp(s_ch, "all") == 0){
				for(i=0; i<MIDI_CH_N; i++) ch_inf[i].adj_vol = v;
			}else{
				int ch = strtol(s_ch, NULL, 0);
				if(0 <= ch && ch < MIDI_CH_N) ch_inf[ch].adj_vol = v;
			}
			if(*s == ',') s++;
		}
	}

prog41 から prog42 の差分です prog42.patch


音色の名前をCUIに追加

せっかく音色の構造体で名前の文字列を追加したので、 CUIでパラメータを表示するときに名前も表示するようにしておきます

cui_tone.c の処理を見てみると

あー、なんとなく思い出してきた

idxのポップアップメニューの右にラベルを追加してみます。
編集することなく固定の表示なので、"prog=", "tone_n=" のラベルの処理と同様にしておけばいいですね。

prog42 から prog43 の差分です prog43.patch

chを1にセットすると

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 1 | note <69 >|                                   |   |
||prog=35  tone_n=1  idx 0|  bass                      |   |
||+-- VCO ----------------------------+                |   |
|||wave1 saw   |   wave2 square|      |                |   |
|||                tune <0     >| cent|                |   |
|||                mix <0.5 >|        |                |   |
|||                [ ] ring           |                |   |
||+-----------------------------------+                |   |
||+-- Filter ------------------------------+           |   |
|||filter1 LPF|         filter2 OFF|       |           |   |
|||freq1 <1000   >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <1.5  >|  reso2 (Q2) <0    >||           |   |
||+----------------------------------------+           |   |
||+-- ENV ----------------+                            |   |
|||attack  <0.05 >| sec   |                            |   |
|||decay   <0.2  >| sec   |                            |   |
|||sustain <0.4  >| (0..1)|                            |   |
|||release <0.2  >| sec   |                            |   |
||+-----------------------+                            |   |
||level <1    >|                                       |   |
||+-- LFO modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <60    >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||+-- LFO --------------------------------------------+|   |
|||wave sin   |  freq <1.5    >| Hz  delay <0.2 >| sec||   |
||+---------------------------------------------------+|   |
||+-- ENV modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <0     >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |
||+-- delay ------------------------+                  |   |
|||[ ] ON  <0   >| sec  gain <0   >||                  |   |
||+---------------------------------+                  |   |
||[X] chorus                                           |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

'bass' と表示され

chを9にしてnoteを40にセットすると

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <40 >|                                   |   |
||prog=0   tone_n=1  idx 0|  snare                     |   |
||+-- VCO ----------------------------+                |   |
|||wave1 square|   wave2 noise |      |                |   |
|||                tune <0     >| cent|                |   |
|||                mix <0.8 >|        |                |   |
|||                [X] ring           |                |   |

'snare'

noteを42にセットすると

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <42 >|                                   |   |
||prog=0   tone_n=1  idx 0|  hi-hat close              |   |
||+-- VCO ----------------------------+                |   |
|||wave1 square|   wave2 square|      |                |   |
|||                tune <700   >| cent|                |   |
|||                mix <0.9 >|        |                |   |
|||                [X] ring           |                |   |
||+-----------------------------------+                |   |

'hi-hat close'

OKです


パラメータのsave/load

さて、先送りしてたパラメータのsave/loadに取り組くんでみましょう

といいつつ、ちょっと脱線

いつもは非力なマシンでぼちぼち試しておる訳ですが、 ちょいと強力なマシンの環境が転がってて、そいつでお試してみたら驚き!

今までのストレスが何だったのかという程、ちょー速いです。

こんな感じで試してみました

のろいマシンにソースコードを一式用意して
  cui/
  midi_prog/

$ tar cf - cui midi_prog | ssh <強力マシン> "cd <作業場所> ; tar xf - ; cd cui ; make ; cd ../midi_prog ; make"

などと強力マシンにソースを送りこんでビルド

$ (cd cui ; make)

と、手前の遅いマシンもcui/cui_srvをビルドしておいて

$ cui/cui_srv -port 9104

手前の遅いマシンで cui_srv を port 9104 で起動

手前の遅いマシンの別端末から

$ cat L3007_06.MID | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog43 -conn <遅いマシン>:9104" | play -t raw -r 44100 -b 16 -c 2 -s -

強力マシンにMIDIファイルの内容を送りつけて、
向こうでビルドしたprog43を起動して、
CUI画面は手前の遅いマシンのcui_srvに接続して、
生成した波形データをrawのまま標準出力からssh経由で受け取って、
手前の遅いマシンのsoxのplayコマンドに接続して音を鳴らしてます

;;; 強力マシンには、soxが入ってませんので

これで、soxでunder-runが全然でません
cui_srvの画面でパラメータを変更すると、鳴ってる音に反映されます
CPUパワーおそるべし!

という事でした

さて戻ってパラメータのsave/load

CUIでパラメータの変更を試す環境は整いつつかるので、 その試したパラメータをファイルに保存できれば良さそうです

loadは起動時に、コマンド引数で保存したファイルが指定されてれば行なうと

途中でCUIからもload出来ても、それもまた良しと

データの形式をどうすべしか?

tone.h

struct tone_rec{
	char *name;
	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{
	char *name;
	int note; /* -1 : event note */
	double rate;
	struct tone_rec *tone;
};

tone.c

static 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 []){
			{ "bass drum", 28, 1.0, }, { NULL, }
		}
	},{
  :

vco.h

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

filter.h

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

env.h

struct env_rec{
	double attack;	/* sec */
	double decay;	/* sec */
	double sustain;	/* 0..1 */
	double release;	/* sec */
};

  :

などなどのパラメータをファイルに保存したい訳ですが...
音色ごとにファイルとかにすると、ややこしそうなので、とりあえず全部一式ファイルに落す方針でいいかな。
パラメータの編集はCUI上でだけするなら、バイナリでもいいか?
でも、やっぱりテキストエディタで編集したいような気も...
バイナリなら環境によってエンディアンとパディング、へたしたら並び順とかまで気にしないとだし。
まてよ...
CUIでは存在する分の変更はできても、新たな追加ができないな。
テキストならとりあえず保存して、テキストファイル上で追加して、それをloadすれば追加、とかで良さそう。
バイナリはそうはいかなくて、やっぱりソースコード上のtone.cの初期化に、先に追加しておいてCUIで編集すべしか。
それ以前に、CUIでは今演奏中のMIDIチャンネルに指定されているプログラム番号の、成分の音色のパラメータしか修正できないな。
むむむ

底のデータとしてはintかdoubleか文字列くらいなもので

例えば構造体の定義とインスタンスがあったとして、 各メンバを指定すれば、そのオフセットとサイズくらいならば、すぐ分かるはず

struct foo{ int a; double b; char *name; } bar;

なら

sizeof(bar.b) は doubleのサイズが返るだろうし
(char*)&bar.b - (char*)&bar とかすれば、
メンバbの先頭からのオフセットバイトが返るだろうし

でも型がdoubleとは、わからんかな〜
gcc拡張ならっていうのはあるのだろうか?
としても、あまり環境に左右されたくないし

とりあえず、最善な方法は先送り。 一旦、バイナリで一括記録。 互換性なくても良しとすべしか

新規追加とかの問題については、 演奏中のCUIによるパラメータの修正とは別に、 新たなツールを用意してなんとかすべしかな

バイナリファイルを読み込んで、 編集できて新規追加もできて、セーブしなおせるるやつを、 本体とは別に作るべしか


エイリアスノイズ選択の自由

ロックンロールウィドゥ、はははん

パラメータの形式で煮詰まってしまったので、脱線して気分転換

以前にエイリアスノイズで、ノイズ除去対策をしてみたら、 やかんを叩くようなハイハットの音が、牛乳瓶を叩くような音に変わりました。

そこで少しパラメータをいじってみたのですが、リング・モジュレーションだけでは、 以前のやかんノイズの再現が難しく、音色によってはかえって不便になってしまったようです。

それならばということで、エイリアスノイズの有無もパラメータで指定できるようにしてみます。

そもそもノイズ除去のため変更はどうだったか? prog29.patch

ふむふむ。
追加した wave_out2() の内容をよく見てみると

そうそう。 ノコギリ波と矩形波の積分したときの波形を用意してて、 一つ前のサンプリングの時の積分値と、今回の積分値を求めて、 引き算して割算で、微分になって、ノイズを含まない波形が得られると

さて、このノイズ除去処理の有無を切替えるなら、
処理としてはwave_out2()が該当箇所で、パラメータ指定はvco.hだろうか

vco.h

struct vco_rec{
	int wave1, wave2;
	int tune; /* for vco2 */
	double mix;
	int ring;
};

wave1, wave2 で個別にノイズ除去の有無を自由に指定できるべきで...

wave.h

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3
#define WAVE_INTEGRAL	(1<<8)

このWAVE_INTEGRALの指定は、wave_out2()からwave_out()の間で、内部的に使ってるだけで、 表には出てこないはずで、この指定は wave_out() に対する指定としてだけ有効なもの

なので、vco_rec の wave1, wave2 にノイズ除去有無の情報を埋め込むよりは、 それ用のメンバを追加した方が単純になるはず。

設定としては、デフォルトでノイズ除去はするべきで、 「あえて」除去しないときに、チェックボックスをONにする方がいいでしょう。

int alias_noise1, alias_noise2;

として追加してみます。

この情報をwave_out2()へと渡す必要があるので、引数追加。
呼び出し元の vco_out()にもint waveしかきてないので、引数追加。
さらにその呼び元は、

$ grep vco_out *.c
tone.c:	v1 = vco_out(vco->wave1, &stat->vco[0], freq, ot->sec, modu_v[0]);
tone.c:	v2 = vco_out(vco->wave2, &stat->vco[1], freq, ot->sec, modu_v[1]);
vco.c:vco_out(int wave, struct vco_stat_rec *stat, double freq, double sec, double modu_v)

tone.c からで、vco->wave1 とか指定してるから、大丈夫ですね。
vco->alias_noise1 とか追加すればOKです

変更の範囲は音色のデフォルト設定 tone.c へのパラメータ追加と、 CUI設定 cui_tone.c への追加

tone.c へのパラメータ追加が面倒なので、vco.c vco_recへのメンバ追加を末尾にします。
これで、指定なければ 0 初期化でデフォルト設定です ;-p)

prog43 から prog44 の差分です prog44.patch

$ cui/cui_srv -port 9105
$ cat L3007_06.MID | midi_prog/prog44 -conn 9105 -play -V0 -q -vol all:0,9:3

うーむ。のろいマシンでは、under-run でまくりで音が途切れるか...
が、それでもハイハットの音が昔のやつ寄りに戻ってる気がします

cui_srv から ch 9 選択して note 42 を指定すると

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <42 >|                                   |   |
||prog=0   tone_n=1  idx 0|  hi-hat close              |   |
||+-- VCO -------------------------------+             |   |
|||wave1 square|      wave2 square|      |             |   |
|||[X] alias_noise1   [X] alias_noise2   |             |   |
|||                   tune <700   >| cent|             |   |
|||                   mix <0.9 >|        |             |   |
|||                   [X] ring           |             |   |
||+--------------------------------------+             |   |

hi-hat close で alias_noise1, 2 とも確かに ON になってて

ch 0 とかに変えてみると

========================= cui_srv ==========================
|menu| / 1 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <42 >|                                   |   |
||prog=50  tone_n=2  idx 0|  lead                      |   |
||+-- VCO -------------------------------+             |   |
|||wave1 square|      wave2 square|      |             |   |
|||[ ] alias_noise1   [ ] alias_noise2   |             |   |
|||                   tune <10    >| cent|             |   |
|||                   mix <0.5 >|        |             |   |
|||                   [ ] ring           |             |   |
||+--------------------------------------+             |   |

こちらはチェックボックス OFF

ch 9 の note 42 に戻して
チェックボックスを操作してOFFに変えてみると

========================= cui_srv ==========================
|menu| / 1 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <42 >|                                   |   |
||prog=0   tone_n=1  idx 0|  hi-hat close              |   |
||+-- VCO -------------------------------+             |   |
|||wave1 square|      wave2 square|      |             |   |
|||[ ] alias_noise1   [ ] alias_noise2   |             |   |
|||                   tune <700   >| cent|             |   |
|||                   mix <0.9 >|        |             |   |
|||                   [X] ring           |             |   |
||+--------------------------------------+             |   |

音量を 0.6 から 4 まで上げてみます

||level <4    >|                                       |   |
||+-- LFO modu ----------------------------------+     |   |
|||pitch1  <0     >| cent  pitch2  <0     >| cent|     |   |
|||filter1 <0     >| cent  filter2 <0     >| cent|     |   |
||+----------------------------------------------+     |   |

ちょっと待って設定反映以降の波形データまでくると...牛乳瓶をたたく音に変わりました。

OKです


ミュートはまじめにサボるべし

さらに、パラメータsave/load を先送り、逃避が続きます

調整用ボリュームを追加しましたが、 音を絞っても波形を生成するマシンの負荷は、ほぼ変わりませんでした。
あたり前です。従来通り波形を生成しようと色〜んな重い処理をした後、 最後に 0 をかけ算して、音を消してるからです。

のろいマシンでも、チャンネル1つだけ再生とかなら、リアルタイムで再生して確認できれば幸いなはず。
ということで、ミュートとして0倍に設定されていたら、ちゃんと処理をさぼって、マシンの負荷を減らしてみます。

main.c main()からながめてdate_out()へ

static 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++;
	}
}

note_buf が free なら何もせず。
note_buf が free でなければ note_out()へ。
note.c note_out() ... と、その前に note_buf への割り付けはどうか?
note_buf_search()で空きを探してるのは note_onoff()からになる。
この呼び元は

$ grep note_onoff *.c
main.c:			note_onoff(hi == 9, sec, ch, note, velo);
note.c:note_onoff(int onoff, double evt_sec, int ch, int note, int velo)

main.c に戻ってきて main()の

		ch = low;
		switch(hi){
		case 8:
		case 9:
			note = rd();
			velo = rd();
			note_onoff(hi == 9, sec, ch, note, velo);
			break;

ここから

そもそもここで、note_onoff()呼ばなければ、音は出ないはず

prog44 から prog45 の差分です prog45.patch

--- midi_prog-/main.c	Thu Mar 26 00:00:00 2015
+++ midi_prog/main.c	Fri Mar 27 02:00:00 2015
@@ -112,7 +112,7 @@
 		case 9:
 			note = rd();
 			velo = rd();
-			note_onoff(hi == 9, sec, ch, note, velo);
+			if(ch_inf[ch].adj_vol > 0) note_onoff(hi == 9, sec, ch, note, velo);
 			break;
 		case 0xb: /* control change */
 			type = rd();

実質この1行だけ

$ cat L3007_06.MID | midi_prog/prog45 -srv 9106 -vol all:0,9:1 -play

のろいマシンでも、ドラムだけ途切れずに鳴ってます


$ cat L3007_06.MID | midi_prog/prog45 -srv 9106 -vol all:0,9:1,1:1 -play

ドラム + ベースでもOK

$ cat L3007_06.MID | midi_prog/prog45 -srv 9106 -vol all:0,0:1 -play

ch 0 の主旋律だけにしてみると、under-runでまくり...
まぁ、和音も多いだろうけど 

別端末から

$ cui/cui_srv
起動して menu / conn で local の port 9106 で接続

ch のメニューを 1, 0 と変化させて ch 0 のパラメータを表示

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|                                   |   |
||prog=50  tone_n=2  idx 0|  lead                      |   |
||+-- VCO -------------------------------+             |   |
|||wave1 square|      wave2 square|      |             |   |
|||[ ] alias_noise1   [ ] alias_noise2   |             |   |
|||                   tune <10    >| cent|             |   |
|||                   mix <0.5 >|        |             |   |
|||                   [ ] ring           |             |   |
||+--------------------------------------+             |   |
||+-- 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 ------------------------+                  |   |
|||[X] ON  <0.2 >| sec  gain <0.4 >||                  |   |
||+---------------------------------+                  |   |
||[ ] chorus                                           |   |
|+-----------------------------------------------------+   |

delay の ON が重そうなので OFF に

||+-- delay ------------------------+                  |   |
|||[ ] ON  <0.2 >| sec  gain <0.4 >||                  |   |
||+---------------------------------+                  |   |

まだ、若干途切れるので filter1 を OFFに

||+-- Filter ------------------------------+           |   |
|||filter1 OFF|         filter2 OFF|       |           |   |
|||freq1 <2000   >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <4    >|  reso2 (Q2) <0    >||           |   |
||+----------------------------------------+           |   |

あっさりした音になりましたが、これならのろいマシンでも、あまり途切れず再生できました


音量調整はCUIから操作できねば

まだまだ逃避が続きます

調整用ボリュームなのに、起動時のオプション指定しか出来ないのは、名前負けしてますね。
をCUIからも操作できるようにしておきます

prog45 から prog46 の差分です prog46.patch

$ cui/cui_srv -port 9107

別端末から
$ cat L3007_06.MID | midi_prog/prog46 -conn 9107 -vol all:0,9:1.5 -play

ドラムの音だけ鳴り始めてます

cui_srv 画面
ch を 9 に、note を 42 に変更すると

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <42 >|   adj_vol <1.5  >|                |   |
||prog=0   tone_n=1  idx 0|  hi-hat close              |   |
  :

指定通り adj_vol 1.5 の表示

ch を 2 に変更して

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 2 | note <42 >|   adj_vol <0    >|                |   |
||prog=48  tone_n=1  idx 0|  strings                   |   |
  :

adj_vol を...
うう、フォーカス移動しにくい...
下の方にある tune から右矢印で、なんとかフォーカス adj_vol へ
0 から 1 に変更してみると

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 2 | note <42 >|   adj_vol <1    >|                |   |
||prog=48  tone_n=1  idx 0|  strings                   |   |
||+-- VCO -------------------------------+             |   |
|||wave1 saw   |      wave2 saw   |      |             |   |
|||[ ] alias_noise1   [ ] alias_noise2   |             |   |
|||                   tune <12    >| cent|             |   |
|||                   mix <0.5 >|        |             |   |
|||                   [ ] ring           |             |   |
||+--------------------------------------+             |   |

strings の音色が鳴りだしました
OKです


音色修正

なかなかsave/loadにいかない ...

どうも L5003_02.MID を食わせたときの、音色がよろしくありません。

まぁドラム系以外は、"strings", "bass", lead", "SIN" の 3つの音色だけでやりくりしてる訳ですが、にしてもちょっと「しどい」

とりあえずで設定したパラメータを眺めてみると

tone.c

	},{
		/* for tong poo */

		26, -1, /* acoustic guitar (steel) */
		(struct tone_compo_rec []){
			{ "strings", -1, 1.0 }, { NULL, }
		}
  :
	},{
		62, -1, /* brass section */
		(struct tone_compo_rec []){
			{ "lead", -1, 1.0 }, { NULL, }
		}

このあたり、
"strings" と "lead" を入れ換えてみたところ ...
なんか、かなりいい感じになりました

prog46 から prog47 の差分です prog47.patch

prog47_xaa.mp3
prog47_xab.mp3
prog47_xac.mp3

さらに 「Behind the Mask」L3007_02.MID

$ cat L3007_02.MID | ./prog_onoff9 | grep prog
sec=0.464 prog num ch=0 v=95
sec=0.488 prog num ch=1 v=38
sec=0.511 prog num ch=2 v=62
sec=0.535 prog num ch=3 v=50
sec=0.559 prog num ch=4 v=103
sec=0.582 prog num ch=5 v=28
sec=0.606 prog num ch=6 v=118
sec=0.630 prog num ch=7 v=108
sec=0.653 prog num ch=8 v=81
sec=0.677 prog num ch=9 v=0
sec=0.701 prog num ch=10 v=25
$ 

     95	Pad 7 (halo)	パッド7(halo)
     38	Slap Bass 2	スラップベース2
既出 62	Brass Section	ブラスセクション
既出 50	String Ensemble 2	ストリングスアンサンブル2
     103	FX 7 (echoes)	FX 7 (echoes)
     28	Electric Guitar (clean)	エレクトリックギター(クリーン)
     118	Melodic Tom	メロディックタム
     108	Koto	琴
既出 81	Lead 1 (square)	リード1(矩形波)
     25	Acoustic Guitar (nylon)	アコースティックギター(ナイロン弦)

$ cat L3007_02.MID | ./prog_onoff9 | grep 'on.*ch=9' | sed -e  's/.*\(note=[0-9]*\) .*/\1/' | sort | uniq
note=36
note=40
note=42
note=46
note=49
$ 

ドラムは既に設定済なものばかり

音色追加しておきます。
といっても、"strings", "lead", "bass", "SIN" のどれかを割りあてるだけ ;-p)

prog47 から prog48 の差分です prog48.patch

prog48_xaa.mp3
prog48_xab.mp3


saveの準備

音色のパラメータを保存するための前準備を進めます

ようやくです

まず tone.c

static struct tone_rec tone_inf[] = {
	{
		"strings",
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
  :
		{ 25, OFF, OFF, OFF }, { WAVE_SIN, 6, 0.3 },
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{
		NULL,
	}
}, drum_tone_inf[] = {
	{
		"bass drum",
		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
		{ LPF, 400, 1 }, { OFF, },
		{ 0.01, 0.18, 0, 0.18 },
  :
		{ OFF, OFF, OFF, OFF },
		{ OFF, }, OFF,
	},{
		NULL,
	}
};

ここで 名前で引っ張ってくるように変更したので、 もはや、ドラム用に分けておく意味もありません。
1つにまとめておきます

--- midi_prog-/tone.c	Tue Mar 31 02:00:00 2015
+++ midi_prog/tone.c	Wed Apr  1 00:00:00 2015
@@ -41,10 +41,6 @@
 		{ OFF, OFF, OFF, OFF },
 		{ OFF, }, OFF,
 	},{
-		NULL,
-	}
-}, drum_tone_inf[] = {
-	{
 		"bass drum",
 		{ WAVE_SIN, WAVE_NOISE, OFF, 0.4, OFF },
 		{ LPF, 400, 1 }, { OFF, },
@@ -297,13 +293,10 @@
 static struct tone_rec *
 name_search(char *name)
 {
-	struct tone_rec *lst[] = { tone_inf, drum_tone_inf, NULL },
-			**p, *tone;
+	struct tone_rec *tone;
 
-	for(p=lst; *p; p++){
-		for(tone=*p; tone->name; tone++){
-			if(strcmp(name, tone->name) == 0) return tone;
-		}
+	for(tone=tone_inf; tone->name; tone++){
+		if(strcmp(name, tone->name) == 0) return tone;
 	}
 	fprintf(stderr, "not found '%s'\n", name);
 	return NULL; /* not found */

続いて追加した名前の指定

ポインタを嫌って追加したはずが、 実はこれまた文字列のポインタになってます

構造体を丸々バイナリでファイルに落すと、 その時のアドレスの値が見える訳ですが、 まぁ、アドレスを単なるIDとして、一致するかどうか? だけを判定するものとして扱う方針で何とかしてみます

同じ文字列については、同じアドレスに合わせておけば、 以降に次のようなデータをファイルに追加しておけば、 ファイルをloadして元どおり再現できるはず

[アドレスの数 N]
[アドレス0], [対応する文字列のオフセット0]
[アドレス1], [対応する文字列のオフセット1]
[アドレス2], [対応する文字列のオフセット2]
  :
[アドレスN-1], [対応する文字列のオフセットN-1]

[文字列用の領域のバイト数]
[対応する文字列0], 
[対応する文字列1], 
[対応する文字列2], 
  :
  :
[対応する文字列N-1]

上記の追加情報もファイルに保存しておいて、 ファイルのロード後は、 構造体中のアドレスの値について、アドレス0,1,2 ... と比較し、 見つかったら、対応する文字列のオフセットを取得。
文字列用の領域の先頭から、オフセット分のバイト数だけ進めたアドレスを、 構造体中に設定しなおせばよしと

さて

static struct tone_rec tone_inf[] = {
	{
		"strings",
		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
		{ LPF, 3000, 1.5 }, { OFF, },
  :
の "strings" と

static struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst[] = {
  :
	},{
		48, -1, /* timpani */
		(struct tone_compo_rec []){
			{ "strings", -1, 1.0 }, { NULL, }
		}
の "strings"

read onlyな文字列リテラルなので、 たいがい同じアドレスを参照してるはずなのでしょうが、 違うアドレスを割り振ることも許されるでしょう

初期化処理で文字列をサーチしてるところで、 アドレスを揃えつつ、ファイルに落す追加データも生成してみます

--- midi_prog--/tone.c	Wed Apr  1 00:00:00 2015
+++ midi_prog/tone.c	Wed Apr  1 01:00:00 2015
@@ -290,6 +290,18 @@
 	}
 };
 
+struct name_lst{
+	char *name;
+	int offset;
+};
+
+static struct{
+	int n;
+	struct name_lst *lst;
+	int area_sz;
+	char *area;
+} name_inf;
+
 static struct tone_rec *
 name_search(char *name)
 {
@@ -302,17 +314,50 @@
 	return NULL; /* not found */
 }
 
+static void
+name_inf_setup(void)
+{
+	struct tone_rec *tone;
+	int i, offset;
+
+	name_inf.n = 0;
+	name_inf.area_sz = 0;
+	for(tone=tone_inf; tone->name; tone++){
+		name_inf.n++;
+		name_inf.area_sz += strlen(tone->name) + 1;
+	}
+	if((name_inf.lst = malloc(sizeof(struct name_lst) * name_inf.n)) == NULL){
+		fprintf(stderr, "No mem");
+		exit(1);
+	}
+	if((name_inf.area = malloc(name_inf.area_sz)) == NULL){
+		fprintf(stderr, "No mem");
+		exit(1);
+	}
+	offset = 0;
+	for(tone=tone_inf, i=0; tone->name; tone++, i++){
+		name_inf.lst[i].name = tone->name;
+		name_inf.lst[i].offset = offset;
+		strcpy(name_inf.area + offset, tone->name);
+		offset += strlen(tone->name) + 1;
+	}
+}
+
 void
 tone_init(void)
 {
 	struct tone_compo_rec *tone_compo;
+	struct tone_rec *tone;
 	int i;
 
 	for(i=0; tones_lst[i].prog >= 0; i++){
 		for(tone_compo=tones_lst[i].tone_compo; tone_compo->name; tone_compo++){
-			tone_compo->tone = name_search(tone_compo->name);
+			tone_compo->tone = tone = name_search(tone_compo->name);
+			if(tone) tone_compo->name = tone->name;
 		}			
 	}
+
+	name_inf_setup();
 }
 
 #define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

ファイルに落すにはまだポインタ問題があります

static 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 []){
			{ "bass drum", 28, 1.0, }, { NULL, }
		}
	},{
  :
	},{
		50, -1, /* strings ensamble 2 */
		(struct tone_compo_rec []){
			{ "lead", -1, 1.0 }, /* test */
			{ "strings", -1, 1.0 }, { NULL, }
		}
  :

tones_lst[] の tone_compo がポインタです

構造体の配列を指してて、構造体の名前のメンバがNULLで終端。
これも名前の文字列と同様の対策でいけそうです

tone.h

struct tone_compo_rec{
	char *name;
	int note; /* -1 : event note */
	double rate;
	struct tone_rec *tone;
};

!!! name 以外にもポインタが !!!

いやいや、これはここで名前を追加したときに、 名前から引っ張ってくるようにしてるので、 ファイルに落す必要のないデータでした。

まぁファイルに無意味なアドレスが記録されてても、別に問題ないでしょう

--- midi_prog---/tone.c	Wed Apr  1 01:00:00 2015
+++ midi_prog/tone.c	Wed Apr  1 02:00:00 2015
@@ -302,6 +302,18 @@
 	char *area;
 } name_inf;
 
+struct compo_lst{
+	struct tone_compo_rec *tone_compo;
+	int idx;	
+};
+
+static struct{
+	int n;
+	struct compo_lst *lst;
+	int arr_n;
+	struct tone_compo_rec *arr;
+} compo_inf;
+
 static struct tone_rec *
 name_search(char *name)
 {
@@ -343,6 +355,40 @@
 	}
 }
 
+static void
+compo_inf_setup(void)
+{
+	struct tone_compo_rec *tone_compo;
+	int i, idx;
+
+	compo_inf.n = 0;
+	compo_inf.arr_n = 0;
+	for(i=0; tones_lst[i].prog >= 0; i++){
+		compo_inf.n++;
+		for(tone_compo=tones_lst[i].tone_compo; tone_compo->name; tone_compo++){
+			compo_inf.arr_n++;
+		}
+		compo_inf.arr_n++;
+	}
+	if((compo_inf.lst = malloc(sizeof(struct compo_lst) * compo_inf.n)) == NULL){
+		fprintf(stderr, "No mem");
+		exit(1);
+	}
+	if((compo_inf.arr = malloc(sizeof(struct tone_compo_rec) * compo_inf.arr_n)) == NULL){
+		fprintf(stderr, "No mem");
+		exit(1);
+	}
+	idx = 0;
+	for(i=0; tones_lst[i].prog >= 0; i++){
+		compo_inf.lst[i].tone_compo = tone_compo = tones_lst[i].tone_compo;
+		compo_inf.lst[i].idx = idx;
+		for(; tone_compo->name; tone_compo++){
+			memcpy(&compo_inf.arr[idx++], tone_compo, sizeof(*tone_compo));
+		}
+		memcpy(&compo_inf.arr[idx++], tone_compo, sizeof(*tone_compo));
+	}
+}
+
 void
 tone_init(void)
 {
@@ -358,6 +404,7 @@
 	}
 
 	name_inf_setup();
+	compo_inf_setup();
 }
 
 #define CH_PROG(ch)	( (ch) == 9 ? 0 : ch_inf[ch].prog )

一旦、ここまでのパッチをまとめておきます

prog48 から prog49 の差分です prog49.patch


saveしてみる

それでは音色のパラメータをファイルに保存してみます

保存すべき内容と順番は次のようなところでしょうか

ファイル名の指定と保存するきっかけはどうすべしか?
やはりCUI画面からでしょうか

とりあえず cui_tone画面の最下行に、 ファイルパスを指定するテキストボックスと、 ボタンを追加してみます

prog49 から prog50 の差分です prog50.patch

$ cui/cui_srv -port 9108

別端末から
$ cat L3007_06.MID | midi_prog/prog50 -conn 9108 -vol all:0,9:1 -play

などとドラムだけ演奏させておいて
cui_srv 画面

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|   adj_vol <0    >|                |   |
||                                                     |   |
  :
||                                                     |   |
||                                                     |   |
||(save)  [tone_file         ]                         |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

とりあえずデフォルトのパスで保存


========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|   adj_vol <0    >|                |   |
||                                                     |   |
  :
||                                                     |   |
||                                                     |   |
||(save)  [/tmp/tone_file    ]                         |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

/tmp/ の下にも保存してみる

別端末から midi_prog/pro50 を起動したディレクトリで
$ ls -lt | head
で確認すると
4513バイトの tone_file が確かに生成

$ ls -lt /tmp | head
にも同じファイルが生成

$ cmp tone_file /tmp/tone_file 
$ 

確かに一致

ヘキサダンプしてみると、こんな感じ

$ hd tone_file
00000000  0c 00 00 00 b8 ab 05 08  01 00 00 00 01 00 00 00  |................|
00000010  0c 00 00 00 00 00 00 00  00 00 e0 3f 00 00 00 00  |...........?....|
00000020  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
00000030  00 70 a7 40 00 00 00 00  00 00 f8 3f 00 00 00 00  |.p.@.......?....|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  33 33 33 33 33 33 c3 3f  00 00 00 00 00 00 e0 3f  |333333.?.......?|
00000060  9a 99 99 99 99 99 e9 3f  00 00 00 00 00 00 e0 3f  |.......?.......?|
00000070  00 00 00 00 00 00 f0 3f  00 00 00 00 0c 00 00 00  |.......?........|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 10 40 33 33 33 33  33 33 d3 3f 00 00 00 00  |...@333333.?....|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000000c0  01 00 00 00 c0 ab 05 08  01 00 00 00 02 00 00 00  |................|
000000d0  00 00 00 00 00 00 00 00  00 00 e0 3f 00 00 00 00  |...........?....|
  :
00001100  c5 ab 05 08 0d 00 00 00  ca ab 05 08 12 00 00 00  |................|
00001110  ce ab 05 08 16 00 00 00  d8 ab 05 08 20 00 00 00  |............ ...|
00001120  de ab 05 08 26 00 00 00  e2 ab 05 08 2a 00 00 00  |....&.......*...|
00001130  ef ab 05 08 37 00 00 00  fb ab 05 08 43 00 00 00  |....7.......C...|
00001140  02 ac 05 08 4a 00 00 00  55 00 00 00 73 74 72 69  |....J...U...stri|
00001150  6e 67 73 00 62 61 73 73  00 6c 65 61 64 00 53 49  |ngs.bass.lead.SI|
00001160  4e 00 62 61 73 73 20 64  72 75 6d 00 73 6e 61 72  |N.bass drum.snar|
00001170  65 00 74 6f 6d 00 68 69  2d 68 61 74 20 63 6c 6f  |e.tom.hi-hat clo|
00001180  73 65 00 68 69 2d 68 61  74 20 6f 70 65 6e 00 63  |se.hi-hat open.c|
00001190  79 6d 62 61 6c 00 73 69  64 65 20 73 74 69 63 6b  |ymbal.side stick|
000011a0  00                                                |.|
000011a1
$ 
確かに末尾は、名前の文字列の領域

とまぁ、とりあえずファイルに書いてみました


loadの準備

次はloadするための準備から

tone.c の状況は

void
tone_init(void)

これは、グローバル変数 tone_inf[], tones_lst[] を
ファイルにsaveするためなどの処理で

void
tone_save(char *path)

で、ファイルに保存

struct tone_compo_rec *
tone_compo_get(int ch, int note, int *ret_n)

では、波形生成からの要求されあたパラメータを返してて、
ここでも、グローバル変数 tones_lst[] を直接参照してます

パラメータ一式をloadで上書きしたいのですが、 グローバル変数 tone_inf[], tones_lst[] は、ともに固定長の配列。
そして tone.c のファイル内のスコープ

ファイル内しか参照してないけど、すでに参照箇所多数。
という事で、tone_inf, tones_lst という名前はポインタに。
初期値用に従来の配列を別の名前に変えておきます。

--- midi_prog-/tone.c	Wed Apr  1 03:00:00 2015
+++ midi_prog/tone.c	Thu Apr  2 00:00:00 2015
@@ -3,7 +3,7 @@
 #include "vcf.h"
 #include "stat.h"
 
-static struct tone_rec tone_inf[] = {
+static struct tone_rec tone_inf_default[] = {
 	{
 		"strings",
 		{ WAVE_SAW, WAVE_SAW, 12, 0.5, OFF },
@@ -106,14 +106,15 @@
 	},{
 		NULL,
 	}
-};
+},
+*tone_inf = tone_inf_default;
 
 #define PROG_DRUM	0
 
 static struct{
 	int prog, note; /* note for ch 9 */
 	struct tone_compo_rec *tone_compo;
-} tones_lst[] = {
+} tones_lst_default[] = {
 	{
 		PROG_DRUM, 36, /* bass drum1 */
 		(struct tone_compo_rec []){
@@ -288,7 +289,8 @@
 	},{
 		-1, /* tail */
 	}
-};
+},
+*tones_lst = tones_lst_default;
 
 struct name_lst{
 	char *name;

そして、ファイルからロードする tone_load() を追加しつつ、 tone_inf[], tones_lst[] のデータ数の情報を保持する変数を追加

そして、さらにポインタをIDと見なして、サーチして付け替える処理を追加しつつ、 tone_init() でやってる名前のサーチもやっておくべしと...

prog50 から prog51 の差分です prog51.patch

とりあえずコンパイルだけ通してます


loadしてみる

では、loadを試してみようかと

が、その前に。
compo_inf, name_inf で追加してるlst[]の、 ポインタ値を更新する処理が抜けてました。
ここを更新しておかないと、loadした後パラメータを更に修正して、 saveしようとしたときに、問題が出るはずです

--- midi_prog-/tone.c	Thu Apr  2 00:00:00 2015
+++ midi_prog/tone.c	Thu Apr  2 01:00:00 2015
@@ -578,6 +578,13 @@
 			tone_compo->tone = name_search(tone_compo->name);
 		}
 	}
+
+	for(i=0; i<name_inf.n; i++){
+		name_inf.lst[i].name = name_inf.area + name_inf.lst[i].offset;
+	}
+	for(i=0; i<compo_inf.n; i++){
+		compo_inf.lst[i].tone_compo = compo_inf.arr + compo_inf.lst[i].idx;
+	}
 }
 
 void

さて、load。
当初は、コマンド引数でファイル名指定でもとか、書いてました。

標準入力からMIDIファイルのデータを受ける前に、パラメータのデータを流し込んではどうでしょうか。
引数で指定したとしても、どうせ初期化処理中にloadするわけだし

main.c

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;
	pthread_t pth;

	tone_init();
	out_init(&otr, ac, av);
	note_buf_init();
	stat_buf_init();
	ch_init(ac, av);

	if(pthread_create(&pth, NULL, pth_func, av)) ERR("pthrad_create");

	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 */
  :

tone_init() があって、header() 呼び出しから

static 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 */
}

標準入力から "MThd" が入ってくることを期待してます

ここの前に割り込ませて、必ず音色パラメータのデータが来ないとダメ。
とかにしてしまうのはイヤですね

rd.c

static 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;
}

一応、古来からの教えを守ってなのか、
1文字だけ先読みしてプッシュバック可能にしてましたね
$ cat L3007_06.MID | hd | less

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|
  :

に比べてみて

$ (echo load ; cat tone_file L3007_06.MID) | hd | less

00000000  6c 6f 61 64 0a 0c 00 00  00 58 4a 41 00 00 00 00  |load.....XJA....|
00000010  00 01 00 00 00 01 00 00  00 0c 00 00 00 00 00 00  |................|
  :
00001820  63 79 6d 62 61 6c 00 73  69 64 65 20 73 74 69 63  |cymbal.side stic|
00001830  6b 00 4d 54 68 64 00 00  00 06 00 00 00 01 00 60  |k.MThd.........`|
00001840  4d 54 72 6b 00 01 6f 31  00 ff 03 0f 54 68 65 20  |MTrk..o1....The |
  :

などと試してみつつ

"load\n" か "MThd" か

5文字先読みしてみて、"load\n" じゃなかったら、 5文字プッシュバックしたいところですな

--- midi_prog-/rd.c	Sat Jan 25 00:00:00 2014
+++ midi_prog/rd.c	Thu Apr  2 01:00:00 2015
@@ -1,22 +1,21 @@
 #include "rd.h"
 
-static int bk_buf = -1;
+#define BK_BUF_N	256
+static int bk_buf[ BK_BUF_N ], bk_buf_n = 0;
 
 int
 rd(void)
 {
-	int v;
-
-	if((v = bk_buf) < 0) return getchar();
-	bk_buf = -1;
-	return v;
+	if(bk_buf_n <= 0) return getchar();
+	return bk_buf[ --bk_buf_n ];
 }
 
 void
 bk(int v)
 {
-	if(bk_buf >= 0) ERR("give up");
-	bk_buf = v;
+	if(bk_buf_n >= BK_BUF_N) ERR("give up");
+	bk_buf[ bk_buf_n++ ] = v;
 }
 
 static void

これでよし

rd.c いじってみて util.h に ERR() マクロがあったのを思いだしました。
tone.c に追加したエラー処理も ERR() マクロ使うように修正しておきます

そしてmain.cにload処理の追加なのですが、 とりあえずコマンド引数は不要になったので、tone.c tone_init() の末尾に追加します

prog51 から prog52 の差分です prog52.patch

$ cat L3007_06.MID | midi_prog/prog52 -srv 9109 -sox prog52.wav

などと従来通り .wav を生成しつつ
別端末から

$ play prog52.wav

で、いつもの音色である事を確認

$ (echo load ; cat tone_file L3007_06.MID) | midi_prog/prog52 -srv 9110 -sox prog52-2.wav

などと保存してあった tone_file をロードして、.wav を生成

$ play prog52-2.wav

これまた、いつもの音色である事を確認

$ cmp prog52.wav prog52-2.wav
$ 

とりあえず、変な事は起こってないと

それでは
$ cui/cui_srv -port 9111

別端末から
$ cat L3007_06.MID | midi_prog/prog52 -conn 9111 -play

cui_srv 画面で
ch 9 の note 42 を選んで、closed hi-hat の音色

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <42 >|   adj_vol <1    >|                |   |
||prog=0   tone_n=1  idx 0|  hi-hat close              |   |
||+-- VCO -------------------------------+             |   |
|||wave1 square|      wave2 square|      |             |   |
|||[X] alias_noise1   [X] alias_noise2   |             |   |
|||                   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.15 >| sec   |                            |   |
|||sustain <0    >| (0..1)|                            |   |
|||release <0.15 >| 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                                           |   |
||(save)  [tone_file         ]                         |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+


alias_noise1, 2 とも ON から OFF に変更

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 9 | note <42 >|   adj_vol <1    >|                |   |
||prog=0   tone_n=1  idx 0|  hi-hat close              |   |
||+-- VCO -------------------------------+             |   |
|||wave1 square|      wave2 square|      |             |   |
|||[ ] alias_noise1   [ ] alias_noise2   |             |   |
|||                   tune <700   >| cent|             |   |
|||                   mix <0.9 >|        |             |   |
|||                   [X] ring           |             |   |
||+--------------------------------------+             |   |


tone_file2 としてsave

||+-- delay ------------------------+                  |   |
|||[ ] ON  <0   >| sec  gain <0   >||                  |   |
||+---------------------------------+                  |   |
||[ ] chorus                                           |   |
||(save)  [tone_file2        ]                         |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+


つづいて filter1 を LPF から OFF に変更

||+-- Filter ------------------------------+           |   |
|||filter1 OFF|         filter2 OFF|       |           |   |
|||freq1 <16000  >| Hz  freq2 <20     >| Hz|           |   |
|||reso1 (Q1) <2    >|  reso2 (Q2) <0    >||           |   |
||+----------------------------------------+           |   |


tone_file3 として save

||+-- delay ------------------------+                  |   |
|||[ ] ON  <0   >| sec  gain <0   >||                  |   |
||+---------------------------------+                  |   |
||[ ] chorus                                           |   |
||(save)  [tone_file3        ]                         |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

おや?
なぜか tone_file2, tone_file3 ファイルサイズが 1801 バイトしかない。
はて?

tone.c

static void
save_n_data(int n, int usz, void *p, FILE *fp)
{
fprintf(stderr, "n=%d usz=%d\n", n, usz);
	if(fwrite(&n, sizeof(n), 1, fp) != 1) ERR("write");
	if(fwrite(p, usz, n, fp) != n) ERR("write");
}

などとしてみて、実行すると

n=0 usz=192
n=0 usz=12
n=33 usz=8
n=67 usz=20
n=11 usz=8
n=85 usz=1


うははー

void
tone_save(char *path)
{
  :
	sz = sizeof(tone_inf[0]);
	save_n_data(sizeof(tone_inf)/sz, sz, tone_inf, fp);

	sz = sizeof(tones_lst[0]);
	save_n_data(sizeof(tones_lst)/sz, sz, tones_lst, fp);
  :	

sizeof(tone_inf) と sizeof(tone_lst) !
ポインタに変更したんだった
修正

--- midi_prog-/tone.c	Thu Apr  2 01:00:00 2015
+++ midi_prog/tone.c	Thu Apr  2 02:00:00 2015
@@ -480,17 +480,19 @@
 tone_save(char *path)
 {
 	FILE *fp;
-	int sz;
+	int sz, n;
 
 	if((fp = fopen(path, "w")) == NULL){
 		MSG(path); ERR("open");
 	}
 
 	sz = sizeof(tone_inf[0]);
-	save_n_data(sizeof(tone_inf)/sz, sz, tone_inf, fp);
+	n = (tone_inf == tone_inf_default) ? sizeof(tone_inf_default)/sz : tone_inf_n;
+	save_n_data(n, sz, tone_inf, fp);
 
 	sz = sizeof(tones_lst[0]);
-	save_n_data(sizeof(tones_lst)/sz, sz, tones_lst, fp);
+	n = (tones_lst == tones_lst_default) ? sizeof(tones_lst_default)/sz : tones_lst_n;
+	save_n_data(n, sz, tones_lst, fp);
 	
 	sz = sizeof(compo_inf.lst[0]);
 	save_n_data(compo_inf.n, sz, compo_inf.lst, fp);

prog52 から prog53 の差分です prog53.patch

prog52 を prog53 に置き換えて、改めて同じ手順で tone_file2, tone_file3 を作成してみて、
ファイルサイズは 4513 バイトで OK

$ hd tone_file > tone_file.hd
$ hd tone_file2 > tone_file2.hd
$ diff -c tone_file.hd tone_file2.hd

などとしてみると、違い多数
でも、ポインタのアドレスの違いがほとんど
なるほど

うーむ。
alias nose ON から OFF の箇所を探すのは辛いか...

再生してみるべし

$ (echo load ; cat tone_file2 L3007_06.MID ) | midi_prog/prog53 -conn 9111 -vol all:0,9:3 -play

確かに!
やかんを叩いたような closed hi-hat の音が
牛乳瓶をたたいたような音に変わってます


cui_srv 画面

ch 9 の note 42 を選ぶと

|====================== cui_tone =======================|
||ch 9 | note <42 >|   adj_vol <3    >|                ||
||prog=0   tone_n=1  idx 0|  hi-hat close              ||
||+-- VCO -------------------------------+             ||
|||wave1 square|      wave2 square|      |             ||
|||[ ] alias_noise1   [ ] alias_noise2   |             ||
|||                   tune <700   >| cent|             ||
|||                   mix <0.9 >|        |             ||
|||                   [X] ring           |             ||
||+--------------------------------------+             ||

確かに alias_nose1, 2 とも OFF

同様に tone_file3 も確認

$ (echo load ; cat tone_file3 L3007_06.MID ) | midi_prog/prog53 -conn 9111 -vol all:0,9:3 -play

うーむ。
filter1 LPF --> OFFの聞こえかたの違いがよくわらんですが

cui_srv 画面で
ch 9 , note 42 を選んでみると

|====================== cui_tone =======================|
||ch 9 | note <42 >|   adj_vol <3    >|                ||
||prog=0   tone_n=1  idx 0|  hi-hat close              ||
||+-- VCO -------------------------------+             ||
|||wave1 square|      wave2 square|      |             ||
|||[ ] alias_noise1   [ ] alias_noise2   |             ||
|||                   tune <700   >| cent|             ||
|||                   mix <0.9 >|        |             ||
|||                   [X] ring           |             ||
||+--------------------------------------+             ||
||+-- Filter ------------------------------+           ||
|||filter1 OFF|         filter2 OFF|       |           ||
|||freq1 <16000  >| Hz  freq2 <20     >| Hz|           ||
|||reso1 (Q1) <2    >|  reso2 (Q2) <0    >||           ||
||+----------------------------------------+           ||

データ的にはloadで更新されて、filter1 はちゃんと OFF になってました


やっぱしテキスト形式で

パラメータのloadを強力マシンで試してみました

$ tar cf - midi_prog | ssh <強力マシン> "cd <作業場所> ; rm -rf midi_prog ; tar xf - ; cd midi_prog ; make"

$ (echo load ; cat tone_file2 L3007_06.MID ) | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog53 -srv 9112" | play -t raw -r 44100 -b 16 -c 2 -s -
 :
ERR load_n_data() tone.c L519 : No Mem
 :
Done.
$ 

へ?
メモリが無い?
強力マシンに限っては、そんなばかな

tone.c

static void *
load_n_data(int *np, int usz, FILE *fp)
{
	int n;
	void *p = NULL;

	if(fread(np, sizeof(*np), 1, fp) != 1) ERR("read");
	n = *np;
	if((p = malloc(usz * n)) == NULL) ERR("No Mem"); // ここが 519行目
	if(fread(p, usz, n, fp) != n) ERR("read");
	return p;
}

きょうび、malloc()はとりあえず成功して、実際に書き込みにいって初めてメモリが足りないとわかる時代なのに?
ちゃんとNULLが返って失敗しとる

  :
	n = *np;
fprintf(stderr, "n=%d usz=%d\n", n, usz);
	if((p = malloc(usz * n)) == NULL) ERR("No Mem"); // ここが 519行目
  :

などと修正してみて

$ tar cf - midi_prog/tone.c | ssh <強力マシン> "cd <作業場所> ; tar xf - ; cd midi_prog ; make"

$ (echo load ; cat tone_file2 L3007_06.MID ) | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog53 -srv 9112" | play -t raw -r 44100 -b 16 -c 2 -s -
 :
n=12 usz=224
n=134623776 usz=16
ERR load_n_data() tone.c L520 : No Mem
 :
Done.
$ 

134623776 * 16 = 2153980416
2G くらい

強力マシンは32Gくらい積んでるのに...って、そこの問題じゃなくて

きっと int や double のサイズが違うんだ

さっそくバイナリ非互換の罠にかかってしまったようです

こうなるとやっぱしテキスト形式がいいなぁ。
でもせっかく作ったから、ここではバイナリのデータを、テキスト形式に変換したり、バイナリに戻したりするツールでも作ってみます

どんなテキスト形式にするべしか?

だらだら前置きが長い...

形式データ

int tone_inf_n 
 addr name
 int wave1
 int wave2
 int tune
 double mix
 int ring
 int alias_noise1
 int alias_noise2
 int fl_type1
 double freq1
 double Q1
 int fl_type2
 double freq2
 double Q2
 double attack
 double decay
 double sustain
 double release
 double level
 int lfo_modu_pitch1
 int lfo_modu_pitch2
 int lfo_modu_filter1
 int lfo_modu_filter2
 int lfo_wave
 double lfo_freq
 double lfo_delay
 int env_modu_pitch1
 int env_modu_pitch2
 int env_modu_filter1
 int env_modu_filter2
 int delay_onoff 
 double delay_sec
 double delay_gain
 int chorus
int tones_lst_n
 int prog
 int note
 addr tone_compo
int compo_inf_n
 addr tone_compo
 int idx
int compo_arr_n
 addr name
 int note
 double rate
 addr tone
int name_info_n
 addr name
 int offset
int name_info_area_sz
 str name_str

これをファイル prmfmt.txt として保存しておきます

変換ツールの名前をどうしようか...

テキスト形式のパラメータにするから 'txprm'

とりあえず、再帰動作の確認 (といっても今のところネストは1つだけ)

#include <fcntl.h>
#include <sys/mman.h>
#include "util.h"

char *work(char *fmt, int idt);

char *
loop(char *fmt, int idt, int n)
{
	int i;
	char *next = fmt;

	for(i=0; i<n; i++) next = work(fmt, idt);
	return next;
}

char *
work(char *fmt, int idt)
{
	char type[32], name[32];
	int i, n = 2;

	while(*fmt){
		for(i=0; fmt[i]==' '; i++);
		if(i < idt) return fmt;
		if(i > idt){
			fmt = loop(fmt, i, n);
			continue;
		}
		sscanf(fmt+i, "%s %s\n", type, name);

		printf("%d/%s/%s\n", i, type, name);

		while(*fmt && *fmt!='\n') fmt++;
		if(*fmt) fmt++;
	}
	return fmt;
}

int
main(int ac, char **av)
{
	char *fn = opt_str("-f", ac, av, "prmfmt.txt");
	int fd_fmt = open(fn, O_RDONLY);
	struct stat sb;
	size_t len;
	char *fmt;

	if(fd_fmt == -1){
		MSG(fn);
		ERR("open");
	}
	if(fstat(fd_fmt, &sb) == -1){
		ERR("fstat");
	}
	len = sb.st_size;

	fmt = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd_fmt, 0);
	if(fmt == MAP_FAILED){
		ERR("mmap");
	}

	work(fmt, 0);

	munmap(fmt, len);
	close(fd_fmt);
	return 0;
}

ちょと実行してみると

$ ./txprm  | head
0/int/tone_inf_n
1/addr/name
1/int/wave1
1/int/wave2
1/int/tune
1/double/mix
1/int/ring
1/int/alias_noise1
1/int/alias_noise2
1/int/fl_type1
1/double/freq1

$ ./txprm  | tail
1/double/rate
1/addr/tone
0/int/name_info_n
1/addr/name
1/int/offset
1/addr/name
1/int/offset
0/int/name_info_area_sz
1/str/name_str
1/str/name_str

とりあえず大丈夫そう?

では、バイナリの処理を追加してみて実行

$ cat tone_file | ./txprm
int tone_inf_n_0 12
 addr name_0 0x805abb8
 int wave1_0 1
 int wave2_0 1
 int tune_0 12
 double mix_0 0.500000
 int ring_0 0
 int alias_noise1_0 0
 int alias_noise2_0 0
 int fl_type1_0 1

   :

 addr name_10 0x805ac02
 int offset_10 74
int name_info_area_sz_0 85
 str name_str_0 "strings"
 str name_str_1 "bass"
 str name_str_2 "lead"
 str name_str_3 "SIN"
 str name_str_4 "bass drum"
 str name_str_5 "snare"
 str name_str_6 "tom"
 str name_str_7 "hi-hat close"
 str name_str_8 "hi-hat open"
 str name_str_9 "cymbal"
 str name_str_10 "side stick"
 str name_str_11 

ここで停止したまま...

あ!
name_info_area_sz 85 だから、文字列85個を期待してるけど、実は85バイトの意味としてたはず

loadするときは、文字列の個数がわかっても、それぞれのサイズが分からないと、予め領域を確保できません。 なので、いっそ全体のバイト数を先に示しておいて、あとは文字列続く仕様としてました

とりあえず、txprm側で文字列の場合は、特別仕様で対応しておきます

$ cat tone_file | ./txprm | tail
 str name_str_8 "bass"
 str name_str_13 "lead"
 str name_str_18 "SIN"
 str name_str_22 "bass drum"
 str name_str_32 "snare"
 str name_str_38 "tom"
 str name_str_42 "hi-hat close"
 str name_str_55 "hi-hat open"
 str name_str_67 "cymbal"
 str name_str_74 "side stick"
$ 

最後まで変換できました

prog53 から txprm追加分の差分です txprm.patch


そしてバイナリにも戻せるように

txprm に -r オプションをとコーディングしてみると、 逆方向では prmfmt.txt ファイルは不要でした。 (逆方向では、型の情報はテキストファイル自身が持ってるので)

別のツール名前のコマンドにするのも何なので、そのまま txprm の -r オプションとして実装しておきます

txprm への -r オプション追加分の差分です txprm2.patch

$ cat tone_file | ./txprm | ./txprm -r | cmp tone_file -
$ 

一致。
元に戻せました。


ではでは、テキストでパラメータをいじって試してみます。

$ tar cf - midi_prog | ssh <強力マシン> "cd <作業場所> ; rm -rf midi_prog ; tar xf - ; cd midi_prog ; make"
などと、強力マシンでビルド

$ (cd midi_prog ; make)
手前の、のろいマシンでもビルド

$ cat tone_file | (cd midi_prog ; ./txprm) > tone_file.txt
手前のマシンでとりあえずテキストファイルに落しておいて

$ cat tone_file.txt | ssh <強力マシン> "cd <作業場所> ; midi_prog/txprm -r" > tone_file_a
そのまま強力マシンに送って、強力マシンのバイナリに変換して、手前で tone_file_a として保存してみる

tone_file_a のサイズは 5409 バイト
確かに 4513 バイトとは異なってます

$ cat tone_file_a | ssh <強力マシン> "cd <作業場所> ; cd midi_prog ; ./txprm" > tone_file_a.txt
そのバイナリを送ってテキストに変換

$ diff -u tone_file.txt tone_file_a.txt
$ 

確かに一致

では、いつぞや失敗した強力マシンでのパラメータのloadを試してみます
$ (echo load ; cat tone_file_a L3007_06.MID ) | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog53 -srv 9113" | play -t raw -r 44100 -b 16 -c 2 -s -
  :
ERR load_n_data() tone.c L519 : No Mem
  :
Done.

だめ。

static void *
load_n_data(int *np, int usz, FILE *fp)
{
	int n;
	void *p = NULL;

	if(fread(np, sizeof(*np), 1, fp) != 1) ERR("read");
	n = *np;
	if((p = malloc(usz * n)) == NULL) ERR("No Mem");
	if(fread(p, usz, n, fp) != n) ERR("read");
	return p;
}

またしても、ここでエラー

デバッグアウト追加して

$ tar cf - midi_prog/tone.c | ssh <強力マシン> "cd <作業場所> ; tar xf - ; cd midi_prog ; make "

retry

  :
usz=224 n=12
usz=16 n=0
usz=16 n=9
usz=32 n=-1
ERR load_n_data() tone.c L520 : No Mem
  :
Done.

はて?
手前のマシンでは

$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog53 -srv 9114 > /dev/null  
usz=192 n=12
usz=12 n=34
usz=8 n=33
usz=20 n=67
usz=8 n=11
usz=1 n=85

んー
強力マシンでバイナリ保存してみます

$ cui/cui_srv -port 9115

別端末から
$ cat L3007_06.MID | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog53 -conn <のろいマシン>:9115 " > /dev/null

cui_srv画面で
ファイル名 tone_file_b に変更して
saveボタン

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|   adj_vol <0    >|                |   |
||                                                     |   |
  :
||                                                     |   |
||(save)  [tone_file_b       ]                         |   |
|+-----------------------------------------------------+   |
|                                                          |
+----------------------------------------------------------+

$ ssh <強力マシン> "cd <作業場所> ls -l tone_file_b"
でサイズ確認すると 6189 バイト

ち、違う。

$ scp <強力マシン>/<作業場所>/tone_file_b .

とりあえず、手前にコピーして

$ hd tone_file_a | head
00000000  0c 00 00 00 b8 ab 05 08  00 00 00 00 01 00 00 00  |................|
00000010  01 00 00 00 0c 00 00 00  00 00 00 00 00 00 e0 3f  |...............?|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
00000030  00 00 00 00 00 70 a7 40  00 00 00 00 00 00 f8 3f  |.....p.@.......?|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  00 00 00 00 33 33 33 33  33 33 c3 3f 00 00 00 00  |....333333.?....|
00000060  00 00 e0 3f 9a 99 99 99  99 99 e9 3f 00 00 00 00  |...?.......?....|
00000070  00 00 e0 3f 00 00 00 00  00 00 f0 3f 00 00 00 00  |...?.......?....|
00000080  0c 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 00 00 00 00 10 40  33 33 33 33 33 33 d3 3f  |.......@333333.?|

$ hd tone_file_b | head
00000000  0c 00 00 00 90 56 41 00  00 00 00 00 01 00 00 00  |.....VA.........|
00000010  01 00 00 00 0c 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 e0 3f 00 00 00 00  00 00 00 00 00 00 00 00  |...?............|
00000030  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 70 a7 40 00 00 00 00  00 00 f8 3f 00 00 00 00  |.p.@.......?....|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 33 33 33 33  33 33 c3 3f 00 00 00 00  |....333333.?....|
00000070  00 00 e0 3f 9a 99 99 99  99 99 e9 3f 00 00 00 00  |...?.......?....|
00000080  00 00 e0 3f 00 00 00 00  00 00 f0 3f 00 00 00 00  |...?.......?....|
00000090  0c 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|


midi_prog/prmfmt.txt

int tone_inf_n 
 addr name
 int wave1
 int wave2
 int tune
 double mix
 int ring
  :


tone_file.txt

int tone_inf_n_0 12
 addr name_0 0x805abb8
 int wave1_0 1
 int wave2_0 1
 int tune_0 12
 double mix_0 0.500000
 int ring_0 0


適当に比較していってみると...

int は 4バイトで、
addr name が 8バイトの様子

tone_file.txt     tone_file_a                 tone_file_b
----------------  --------------------------  ------------------------
int tone_inf_n    0c 00 00 00                 0c 00 00 00
 addr name        b8 ab 05 08 00 00 00 00     90 56 41 00 00 00 00 00
 int wave1        01 00 00 00                 01 00 00 00
 int wave2        01 00 00 00                 01 00 00 00
 int tune         0c 00 00 00                 0c 00 00 00
 double mix       00 00 00 00  00 00 e0 3f    00 00 00 00 00 00 00 00
 int ring         00 00 00 00                 00 00 e0 3f


double mix で、ズレ

なるほど構造体のpadding

実際はメンバtuneとmixの間に4バイト入ってて、
mixの先頭アドレスが、8の倍数にalignmentされてます

txprmツールはpaddingを考慮せず、きつきつに並べてます

さて、どうしたものか

txprmの対応としては、先頭からのオフセットを保持しつつ、 読み込もうとするデータのサイズの倍数になってないと、 paddingが入ってると考えて、必要分だけスキップします

txprmから-rオプションでバイナリを出すときも、 オフセットを保持しつつ、出力するデータのサイズの倍数になってないと、 必要な分だけpaddingを追加します

こんなのでどうか?

txprm padding修正分の差分です txprm3.patch

$ tar cf - midi_prog | ssh <強力マシン> "cd <作業場所> ; rm -rf midi_prog ; tar xf - ; cd midi_prog ; make"
強力マシンでビルド

$ cat tone_file.txt | ssh <強力マシン> "cd <作業場所> ; midi_prog/txprm -r" > tone_file_c
強力マシンに以前のテキストを送って、強力マシンのバイナリに変換しなおし

tone_file_c のサイズは 6093 バイト
tone_file_b のサイズは 6189 バイト
ま、まだ違う。

$ hd tone_file_c | head
00000000  0c 00 00 00 00 00 00 00  b8 ab 05 08 00 00 00 00  |................|
00000010  01 00 00 00 01 00 00 00  0c 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 e0 3f  00 00 00 00 00 00 00 00  |.......?........|
00000030  00 00 00 00 01 00 00 00  00 00 00 00 00 70 a7 40  |.............p.@|
00000040  00 00 00 00 00 00 f8 3f  00 00 00 00 00 00 00 00  |.......?........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  33 33 33 33 33 33 c3 3f  00 00 00 00 00 00 e0 3f  |333333.?.......?|
00000070  9a 99 99 99 99 99 e9 3f  00 00 00 00 00 00 e0 3f  |.......?.......?|
00000080  00 00 00 00 00 00 f0 3f  00 00 00 00 0c 00 00 00  |.......?........|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

しまった

tone.c

static void
save_n_data(int n, int usz, void *p, FILE *fp)
{
	if(fwrite(&n, sizeof(n), 1, fp) != 1) ERR("write");
	if(fwrite(p, usz, n, fp) != n) ERR("write");
}

サイズ情報の整数と、その後のデータとの間には、paddingの関係は無し

構造体の先頭で offset = 0 にリセットする必要がありそう

prmfmt.txt

int tone_inf_n 
 addr name
 int wave1
  :
 double delay_gain
 int chorus
int tones_lst_n
 int prog
 int note
 addr tone_compo
int compo_inf_n
 addr tone_compo
  :

なので、indent が増えるところで、offset = 0 にすべしか

となると当然バイナリを出力する側の時も、indent を見て対処すべし

txprm padding修正分の差分です txprm4.patch


$ cat tone_file.txt | ssh <強力マシン> "cd <作業場所> ; midi_prog/txprm -r" | hd | head
00000000  0c 00 00 00 b8 ab 05 08  00 00 00 00 01 00 00 00  |................|
00000010  01 00 00 00 0c 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 e0 3f 00 00 00 00  00 00 00 00 00 00 00 00  |...?............|
00000030  01 00 00 00 00 00 00 00  00 70 a7 40 00 00 00 00  |.........p.@....|
00000040  00 00 f8 3f 00 00 00 00  00 00 00 00 00 00 00 00  |...?............|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 33 33 33 33  |............3333|
00000060  33 33 c3 3f 00 00 00 00  00 00 e0 3f 9a 99 99 99  |33.?.......?....|
00000070  99 99 e9 3f 00 00 00 00  00 00 e0 3f 00 00 00 00  |...?.......?....|
00000080  00 00 f0 3f 00 00 00 00  0c 00 00 00 00 00 00 00  |...?............|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|


強力マシンで prog53 から直接saveしたバイナリ tone_file_b と比較

$ hd tone_file_b | head
00000000  0c 00 00 00 90 56 41 00  00 00 00 00 01 00 00 00  |.....VA.........|
00000010  01 00 00 00 0c 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 e0 3f 00 00 00 00  00 00 00 00 00 00 00 00  |...?............|
00000030  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  00 70 a7 40 00 00 00 00  00 00 f8 3f 00 00 00 00  |.p.@.......?....|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 33 33 33 33  33 33 c3 3f 00 00 00 00  |....333333.?....|
00000070  00 00 e0 3f 9a 99 99 99  99 99 e9 3f 00 00 00 00  |...?.......?....|
00000080  00 00 e0 3f 00 00 00 00  00 00 f0 3f 00 00 00 00  |...?.......?....|
00000090  0c 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|


offset 00000030 からズレ


tone_file.txt

int tone_inf_n_0 12
 addr name_0 0x805abb8
 int wave1_0 1
 int wave2_0 1
 int tune_0 12
 double mix_0 0.500000
 int ring_0 0
 int alias_noise1_0 0
 int alias_noise2_0 0
 int fl_type1_0 1
 double freq1_0 3000.000000
 double Q1_0 1.500000
 int fl_type2_0 0
   :

なので

info                            size    offset  local offset
------------------------------  ------  ------  -------------
int tone_inf_n_0 12		4	0	0
 addr name_0 0x805abb8		8	4	0
 int wave1_0 1			4	12	8
 int wave2_0 1			4	16(10h) 12
 int tune_0 12			4	20	16
   <pad>			4	24	20
 double mix_0 0.500000		8	28	24
 int ring_0 0			4	36(24h)	32
 int alias_noise1_0 0		4	40	36
 int alias_noise2_0 0		4	44	40
 int fl_type1_0 1		4	48	44
   :

この fl_type1_0 の値 '1' が既にズレてるはず

そうか

tone.h

struct tone_rec{
	char *name;
	struct vco_rec vco;
	struct filter_rec fl1, fl2;
  :

そして

filter.h

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

filter_rec 先頭で offset 0 にして
type と freq の間に padding が入るはずで

filter_rec構造体のサイズが 4 + 4 + 8 * 2 で、
最大の 8 に合わせたalignment て配置されるはず

prmfmt.txt がフラット過ぎて、構造体の区切りの情報が足りないと...
どうやら、はまってしまったようです

ここで冷静に考えてみます

filter.h

struct filter_rec{
	int type; /* OFF, LPF, HPF, BPF */
	double freq, Q;
};

元々これが含まれてるので、ここはどうなってたのだろう?

$ hd tone_file | head
00000000  0c 00 00 00 b8 ab 05 08  01 00 00 00 01 00 00 00  |................|
00000010  0c 00 00 00 00 00 00 00  00 00 e0 3f 00 00 00 00  |...........?....|
00000020  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  |................|
00000030  00 70 a7 40 00 00 00 00  00 00 f8 3f 00 00 00 00  |.p.@.......?....|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  33 33 33 33 33 33 c3 3f  00 00 00 00 00 00 e0 3f  |333333.?.......?|
00000060  9a 99 99 99 99 99 e9 3f  00 00 00 00 00 00 e0 3f  |.......?.......?|
00000070  00 00 00 00 00 00 f0 3f  00 00 00 00 0c 00 00 00  |.......?........|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 10 40 33 33 33 33  33 33 d3 3f 00 00 00 00  |...@333333.?....|


tone_file.txt

int tone_inf_n_0 12
 addr name_0 0x805abb8
 int wave1_0 1
 int wave2_0 1
 int tune_0 12
 double mix_0 0.500000
 int ring_0 0
 int alias_noise1_0 0
 int alias_noise2_0 0
 int fl_type1_0 1
 double freq1_0 3000.000000
 double Q1_0 1.500000
 int fl_type2_0 0
   :

 int fl_type1_0 1
が
0x28 からの 01 00 00 00

 double freq1_0 3000.000000
が
0x2c からの 00 00 00 00 00 70 a7 40

 double Q1_0 1.500000
が
0x34 からの 00 00 00 00  00 00 f8 3f

 int fl_type2_0 0
が
0x3c からの 00 00 00 00

と考えると続く
freq2 , Qは
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

後続
00000050  33 33 33 33 33 33 c3 3f  00 00 00 00 00 00 e0 3f  |333333.?.......?|
とは

 double attack_0 0.150000
 double decay_0 0.500000
 double sustain_0 0.800000
 double release_0 0.500000
 double level_0 1.000000
   :

decay の 0.5 が mix 0.5 と同じ8バイト列なので、そのはず

これらより、
のろいマシンは double 8 バイトは 8バイトalign じゃなくて 4バイトalign で配置されてるはず

だから、padding考慮しなくても、int, dobule, addr(pointer)
全部 4バイト align 扱いで、ツメツメの解釈で問題が出ませんでした

強力マシンでは、addr(pointer)も8バイトになるは、
double も addr も 8バイトalign になるは、で問題が噴出した

int tone_inf_n_0 12
 addr name_0 0x805abb8 (ここが4バイトから8バイトに増えるが、構造体の先頭なのでpaddingは無し)
 int wave1_0 1
 int wave2_0 1
 int tune_0 12
 double mix_0 0.500000 (ここが8バイトalignになって、直前にpadding 4バイト追加)
 int ring_0 0
 int alias_noise1_0 0
 int alias_noise2_0 0
 int fl_type1_0 1 (構造体の切れ目であり、8バイトalignメンバを含むようになり、ここでも8バイトalin扱いで、直前にpadding 4バイト追加)
 double freq1_0 3000.000000 (8バイトalignに)
 double Q1_0 1.500000 (8バイトalignに)
 int fl_type2_0 0
   :

おそらく、こういう事でしょう


やっぱし最初からテキスト形式で

構造体のpaddingで、はまってます。 では、__attribute___((__packed__)) つけて試すストーリーもありますが、ここは ...
「よし、撤退しよう。」

「艦長、しっぽを巻いて逃げるんですか。」
「ここで戦っても自滅するだけだ。撤退する。」

バイナリ形式を捨てて、最初からテキスト形式で保存する方向で、作戦を練りなおしてみます。

まず、構造体の情報を文字列でひっぱってくる仕組みをこさえてみます。

構造体 (record) なので、rec.[ch]

rec.h

#ifndef __REC_H__
#define __REC_H__

#include <stddef.h>

struct memb{
	char *type, *name;
	int offset, size;
};

struct rec{
	char *name;
	struct memb *membs;
};

#define membsizeof(rec, memb)	sizeof(((rec *)NULL)->memb)
#define MEMB(rec, type, memb)	{ #type, #memb, offsetof(rec, memb), membsizeof(rec, memb) }

struct rec *rec_get(char *name);

#endif


rec.c

#include "tone.h"
#include "rec.h"

struct rec recs[] = {
	{
		"struct tone_rec",
		(struct memb []){
			MEMB(struct tone_rec, char*, name),
			MEMB(struct tone_rec, struct vco_rec, vco),
			MEMB(struct tone_rec, struct filter_rec, fl1),
			MEMB(struct tone_rec, struct filter_rec, fl2),
			MEMB(struct tone_rec, struct env_rec, env),
			MEMB(struct tone_rec, double, level),
			MEMB(struct tone_rec, struct modu_rec, lfo_modu),
			MEMB(struct tone_rec, struct lfo_rec, lfo),
			MEMB(struct tone_rec, struct modu_rec, env_modu),
			MEMB(struct tone_rec, struct delay_rec, delay),
			MEMB(struct tone_rec, int, chorus),
			{ NULL }
		}
	},{
		"struct vco_rec",
		(struct memb []){
			MEMB(struct vco_rec, int, wave1),
			MEMB(struct vco_rec, int, wave2),
			MEMB(struct vco_rec, int, tune),
			MEMB(struct vco_rec, double, mix),
			MEMB(struct vco_rec, int, ring),
			MEMB(struct vco_rec, int, alias_noise1),
			MEMB(struct vco_rec, int, alias_noise2),
			{ NULL }
		}
	},{
		"struct filter_rec",
		(struct memb []){
			MEMB(struct filter_rec, int, type),
			MEMB(struct filter_rec, double, freq),
			MEMB(struct filter_rec, double, Q),
			{ NULL }
		}
	},{
		"struct env_rec",
		(struct memb []){
			MEMB(struct env_rec, double, attack),
			MEMB(struct env_rec, double, decay),
			MEMB(struct env_rec, double, sustain),
			MEMB(struct env_rec, double, release),
			{ NULL }
		}
	},{
		"struct modu_rec",
		(struct memb []){
			MEMB(struct modu_rec, int, pitch1),
			MEMB(struct modu_rec, int, pitch2),
			MEMB(struct modu_rec, int, filter1),
			MEMB(struct modu_rec, int, filter2),
			{ NULL }
		}
	},{
		"struct lfo_rec",
		(struct memb []){
			MEMB(struct lfo_rec, int, wave),
			MEMB(struct lfo_rec, double, freq),
			MEMB(struct lfo_rec, double, delay),
			{ NULL }
		}
	},{
		"struct delay_rec",
		(struct memb []){
			MEMB(struct delay_rec, int, onoff),
			MEMB(struct delay_rec, double, sec),
			MEMB(struct delay_rec, double, gain),
			{ NULL }
		}
	},{
		NULL
	}
};

struct rec *
rec_get(char *name)
{
	struct rec *rec;

	for(rec=recs; rec->name; rec++){
		if(strcmp(rec->name, name) == 0) return rec;
	}
	return NULL;	
}

これを使えば、構造体の中身をテキストで出力できそうです。 メンバの型が構造体なら、再帰的に呼び出せばよさそうです。

recs の中身をよくみると char *name があります。 メンバがchar* をどうするか?

NULLか'\0'ターミネイトの文字列しか扱わないと割り切ってみます。 NULLなら「NULL」と出力して、 それ以外の文字列なら 「"xxxx"」と出力します。

loadするときは、「NULL」ならNULLを設定し、 「"xxxx"」ならmallocで領域確保して設定すればよさそうです。

では、メンバがcharの配列のときは?
メンバの情報の型名として "char[]" としておけば、charの配列と判るわけで、 要素数は、struct memb の size で、char が1バイトに対して、何倍かで判ります。
NULLはありえないので、'\0'ターミネイト文字列だけが許される事にします。

じゃ、intや別の型の配列ならば?
要素数は、charの場合と同様に判りますが、文字列のように 「"xxxx"」 と表現できません。
int か double なら、{ 1, 2, 3 } とか { 123.4, 0, -99.99 } とかにしますかな。
次は構造体。これが問題...

そもそも構造体をテキスト出力したくて、rec.[ch]を使う訳で、もしメンバに構造体が無ければ

1行で
  メンバ名 値
とかで出力すればいいはずです

例えば
struct abc{
  char *name;
  int count;
  char memo[64];
  char *addrs[64];
};
ならば

  name "foo"
  count 3
  param { 1.5, 0, -1.5 }
  memo "ABC"
  addrs { "osaka", "kyoto", "nagoya" }

メンバに構造体がくると
ネストになるわけで、1行でという事にはしにくく

例えば
struct abc{
  char *name;
  int count;
  struct vco_rec vco;
  int size;
};
ならば

  name "foo"
  count 3
  vco
    wave1 0
    wave2 3
      :
  size 256

などと字下げすればいいか?

メンバが構造体の場合は、まぁそれでなんとかなったとして...
戻って、メンバが構造体の配列の場合

  name "foo"
  count 3
  vco
    wave1 0
    wave2 3
      :   
    wave1 1
    wave2 4
      :

切れ目が判りにくい

  vco[0]
    wave1 0
    wave2 3
      :   
  vco[1]
    wave1 1
    wave2 4
      :

メンバ名が何回も出て来るのも変か?

C言語の初期化だと

  struct vco_rec vco[] = {
    { ... },
    { ... },
      :
  };

intやdoubleの配列で '{' と '}' は使ってるから、それは合わせるとして

  vco
  {
    wave1 0
    wave2 3
     :
  ,
    wave1 1
    wave2 4
     :
  }

こんなのでどうだろうか

ではでは、intやdoubleのポインタならどうするか?
とりあえずchain listな構造はあきらめて、 単体の実体あるいは配列を指してる、あるいはNULLであるとします。
文字列のポインタのように、load時にmalloc()で確保して、設定したいところですが...

配列と違うのでサイズはどうすべしか?
値 { 1, 2, 3 } を読み込んで、調べればわかるはずですが、その前に。
そもそもsaveするときに、サイズはどうするか?
ポインタの配列なら、末尾データとしてNULLはよくあるパターンなので、char** ならそれもありかも。 intなら、ターミネータは0かもしれないし-1かもしれないし。
doubleなら、そもそもそんな仕様にはしないかも。

いったんあきらめて、NULLでなければ実体1つとしてsaveで。

なのでint,doubleのポインタはポインタじゃない実体のときと同じような感じ

例えば
struct hoge{
  int *bar;
  double *bar_dbl;
};
ならば

  bar 3
  bar_dbl 12.34

次に、メンバが構造体のポインタの場合。

tone.c

static struct{
	int prog, note; /* note for ch 9 */
	struct tone_compo_rec *tone_compo;
} tones_lst_default[] = {
	{
		PROG_DRUM, 36, /* bass drum1 */
		(struct tone_compo_rec []){
			{ "bass drum", 28, 1.0, }, { NULL, }
		}
	},{
		PROG_DRUM, 37, /* side stick */
		(struct tone_compo_rec []){
			{ "side stick", 110, 1.0 }, { NULL, }
		}
  :

の、メンバ tone_compo
このパターンをとりあえず何とかしたいところ

ポインタの先は配列になってて、
末尾の要素は、NULLが絡んで終端の目印としているパターン
かと思いきや

	},{
		PROG_DRUM, 55, /* splash cymbal */
		(struct tone_compo_rec []){
			{ "cymbal", 75, 1.0 }, { NULL, }
		}
	},{
		-1, /* tail */
	}
},

intで-1にしてたか...

ここは、
	},{
		-1, -1, NULL /* tail */
	}
にしよう。そうしよう。	

末尾判定のルールは次のような感じでどうだろうか

書き出すとややこしいけど、再帰でコーディングすると、すっきりするはずです。
たぶん...

さて、実装できるだろうか...


音色追加

ここらで Tea break

他の曲のMIDIファイルで使ってるプログラム番号をtone.cに追加しておきます。

$ cat L3007_03.MID | ./prog_onoff9 | grep prog
sec=0.408 prog num ch=0 v=30
sec=0.429 prog num ch=1 v=33
sec=0.450 prog num ch=2 v=26
sec=0.471 prog num ch=3 v=49
sec=0.492 prog num ch=4 v=90
sec=0.512 prog num ch=5 v=104
sec=0.533 prog num ch=6 v=63
sec=0.554 prog num ch=7 v=30
sec=0.575 prog num ch=8 v=87
sec=0.596 prog num ch=9 v=0
sec=0.617 prog num ch=10 v=24
sec=0.637 prog num ch=11 v=118
sec=0.658 prog num ch=12 v=127
sec=0.679 prog num ch=13 v=30
sec=176.008 prog num ch=12 v=127

tone.c への追加分は
30	Overdriven Guitar	オーバードライブギター
33	ベース	Acoustic Bass	アコースティックベース
90	Pad 2 (warm)		パッド2(warm)
104	FX 8 (sci-fi)		FX 8 (sci-fi)
63	SynthBrass 1		シンセブラス1
127	Applause		拍手


$ cat L3007_03.MID | ./prog_onoff9 | grep 'on.*ch=9' | sed -e  's/.*\(note=[0-9]*\) .*/\1/' | sort | uniq
note=36
note=38
note=42
note=46
note=47
note=48
note=49
note=50
note=57

tone.c への追加分は
38	Acoustic Snare	アコースティックスネア
47	Low-Mid Tom	ローミッドタム
48	Hi-Mid Tom	ハイミッドタム
50	High Tom	ハイタム


$ cat L3007_07.MID | ./prog_onoff9 | grep prog
sec=0.408 prog num ch=0 v=50
sec=0.429 prog num ch=1 v=38
sec=0.450 prog num ch=2 v=93
sec=0.471 prog num ch=3 v=81
sec=0.492 prog num ch=4 v=80
sec=0.513 prog num ch=5 v=51
sec=0.534 prog num ch=6 v=74
sec=0.554 prog num ch=7 v=87
sec=0.575 prog num ch=8 v=118
sec=0.596 prog num ch=9 v=0
sec=0.617 prog num ch=10 v=86
sec=0.638 prog num ch=11 v=100
sec=0.659 prog num ch=12 v=81

tone.c への追加分は
93	Pad 5 (bowed)		パッド5(bowed)
51	SynthStrings 1		シンセストリングス1
86	Lead 6 (voice)		リード6(ボイス)
100	FX 4 (atmosphere)	FX 4 (atmosphere)


$ cat L3007_07.MID | ./prog_onoff9 | grep 'on.*ch=9' | sed -e  's/.*\(note=[0-9]*\) .*/\1/' | sort | uniq
note=36
note=40
note=42
note=44
note=46
note=47
note=49
note=52
note=55
note=57

tone.c への追加分は
52	Chinese Cymbal	チャイニーズシンバル
55	Splash Cymbal	スプラッシュシンバル

prog53 から prog54 の差分です prog54.patch


やっぱし最初からテキスト形式で (その2)

先の検討結果の実装について、saveからやってみます。

save用の構造体を1つ作って、それをsaveの関数に渡せば、 再帰的に、メンバの構造体もsaveしていく事にします。

出力テキストの先頭には、その根本の構造体の型名だけは必要です。
構造体に含まれる、それ以外の全ての型は、rec.c のしくみでたぐれるはずです。

まず、手始めに

tone.c
	},{
		-1, /* tail */
	}
を
	},{
		-1, -1, NULL /* tail */
	}
にしよう。そうしよう。	

の変更から。

音色の名前も、同じポインタの値にするとか、ややこしい事は無かった事にして、 ざくざくとコードを消していきます。

rec.[ch] に構造体を追加して、テキスト出力処理を追加します。

実際に保存したい情報を並べてみると、配列は無く全てポインタでした。
なので予告してた末尾判定の再帰のやつをばひとつ

void *
memb_addr(void *obj, struct memb *memb)
{
	return ((char*)obj) + memb->offset;
}

int
type_is_rec(char *type)
{
	char *k = "struct ";
	return strncmp(type, k, strlen(k)) == 0;
}

int
type_is_pointer(char *type)
{
	return type[ strlen(type) - 1] == '*';
}

void *
memb_get_pointer(void *obj, struct memb *memb)
{
	return *(void **)memb_addr(obj, memb);
}

int rec_is_tail(void *obj, struct rec *rec);

int
memb_is_tail(void *obj, struct memb *memb)
{
	char *type = memb->type;
	if(type_is_pointer(type)) return memb_get_pointer(obj, memb) == NULL;
	if(type_is_rec(type)) return rec_is_tail(memb_addr(obj, memb), rec_get(type));
	return 0;
}

int
rec_is_tail(void *obj, struct rec *rec)
{
	int i;

	for(i=0; rec->membs[i].type; i++){
		if(memb_is_tail(obj, &rec->membs[i])) return 1;
	}
	return 0;
}

とりあえずコンパイルは通りました。

保存処理をコーディングし始めてみると、 構造体のサイズがすぐ判らないという問題が出てきました。
メンバが構造体のときは、すぐサイズが判るのですが、 構造体の名前だけがある状態から、struct recを取得しても、 自身のサイズが取得できません。

追加したいのですが、結構影響デカいかな?

--- midi_prog--/rec.h	2015-04-10 00:00:00.000000000 +0900
+++ midi_prog/rec.h	2015-04-15 00:00:00.000000000 +0900
@@ -10,12 +10,15 @@
 
 struct rec{
 	char *name;
+	int size;
 	struct memb *membs;
 };
 
+#define REC(rec)		#rec, sizeof(rec)
 #define membsizeof(rec, memb)	sizeof(((rec *)NULL)->memb)
 #define MEMB(rec, type, memb)	{ #type, #memb, offsetof(rec, memb), membsizeof(rec, memb) }
 
--- midi_prog--/rec.c	2015-04-10 00:00:00.000000000 +0900
+++ midi_prog/rec.c	2015-04-15 00:00:00.000000000 +0900
@@ -3,7 +3,7 @@
 
 struct rec recs[] = {
 	{
-		"struct tone_rec",
+		REC(struct tone_rec),
 		(struct memb []){
 			MEMB(struct tone_rec, char*, name),
 			MEMB(struct tone_rec, struct vco_rec, vco),
@@ -19,7 +19,7 @@
 			{ NULL }
 		}
 	},{
-		"struct vco_rec",
+		REC(struct vco_rec),
 		(struct memb []){
 			MEMB(struct vco_rec, int, wave1),
 			MEMB(struct vco_rec, int, wave2),
@@ -31,7 +31,7 @@
 			{ NULL }
 		}
 	},{
-		"struct filter_rec",
+		REC(struct filter_rec),
 		(struct memb []){
 			MEMB(struct filter_rec, int, type),
 			MEMB(struct filter_rec, double, freq),
@@ -39,7 +39,7 @@
 			{ NULL }
 		}
 	},{
-		"struct env_rec",
+		REC(struct env_rec),
 		(struct memb []){
 			MEMB(struct env_rec, double, attack),
 			MEMB(struct env_rec, double, decay),
@@ -48,7 +48,7 @@
 			{ NULL }
 		}
 	},{
-		"struct modu_rec",
+		REC(struct modu_rec),
 		(struct memb []){
 			MEMB(struct modu_rec, int, pitch1),
 			MEMB(struct modu_rec, int, pitch2),
@@ -57,7 +57,7 @@
 			{ NULL }
 		}
 	},{
-		"struct lfo_rec",
+		REC(struct lfo_rec),
 		(struct memb []){
 			MEMB(struct lfo_rec, int, wave),
 			MEMB(struct lfo_rec, double, freq),
@@ -65,7 +65,7 @@
 			{ NULL }
 		}
 	},{
-		"struct delay_rec",
+		REC(struct delay_rec),
 		(struct memb []){
 			MEMB(struct delay_rec, int, onoff),
 			MEMB(struct delay_rec, double, sec),

続いて、構造体を再帰的にテキスト出力する処理

rec.c に追加

static void
idt_out(int idt, char *s, FILE *fp)
{
	int i;
	for(i=0; i<idt; i++) fputc(' ', fp);
	fputs(s, fp);
}

static void
non_rec_out(char *type, void *addr, FILE *fp)
{
	if(strcmp(type, "char") == 0){
		fprintf(fp, "%c", *(char*)addr);
	}else if(strcmp(type, "int") == 0){
		fprintf(fp, "%d", *(int*)addr);
	}else if(strcmp(type, "double") == 0){
		fprintf(fp, "%lf", *(double*)addr);
	}else{
		MSG(type);
		ERR("unkown");
	}
}

void rec_save(void *obj, struct rec *rec, FILE *fp, int idt);

void
memb_save(void *obj, struct memb *memb, FILE *fp, int idt)
{
	char *type = memb->type;
	void *addr = memb_addr(obj, memb);
	void *p;
	char strip_type[ TYPE_MAX ];

	idt_out(idt, memb->name, fp);

	if(!type_is_pointer(type)){
		if(type_is_rec(type)){
			fputc('\n', fp);
			rec_save(memb_addr(obj, memb), rec_get(memb->type), fp, idt+2);
			return;
		}
		fputc(' ', fp);
		non_rec_out(type, addr, fp);
		fputc('\n', fp);
		return;
	}

	/* type is pointer */

	if((p = memb_get_pointer(obj, memb)) == NULL){
		fputs(" NULL\n", fp);
		return;
	}
	memset(strip_type, 0, sizeof(strip_type));
	memcpy(strip_type, type, strlen(type)-1);

	if(type_is_rec(strip_type)){
		struct rec *rec = rec_get(strip_type);
		fputc('\n', fp);
		idt_out(idt, "{\n", fp);
		for(;;){
			rec_save(p, rec, fp, idt+2);
			if(rec_is_tail(p, rec)) break;
			idt_out(idt, ",\n", fp);
			p = ((char*)p) + rec->size;
		}
		idt_out(idt, "}\n", fp);
		return;
	}
	fputc(' ', fp);
	if(strcmp(strip_type, "char") == 0){
		fprintf(fp, "\"%s\"", (char*)p);
	}else{
		non_rec_out(strip_type, p, fp);
	}
	fputc('\n', fp);
}

void
rec_save(void *obj, struct rec *rec, FILE *fp, int idt)
{
	int i;

	for(i=0; rec->membs[i].type; i++) memb_save(obj, &rec->membs[i], fp, idt);
}

void
rec_save_text(void *obj, char *name, FILE *fp)
{
	struct rec *rec;

	if((rec = rec_get(name)) == NULL){
		MSG(name);
		ERR("not found");
	}
	fprintf(fp, "%s\n", name);

	rec_save(obj, rec, fp, 0);
}

そしてtone.cから

void
tone_save(char *path)
{
	FILE *fp;
	struct tone_all_rec all = { tone_inf, tones_lst };

	if((fp = fopen(path, "w")) == NULL){
		MSG(path); ERR("open");
	}
	rec_save_text(&all, "struct tone_all_rec", fp);
	fclose(fp);
}

などと呼び出して試します

prog54 から prog55 の差分です prog55.patch

$ cui/cui_srv -port 9116

別端末から
$ cat L3007_06.MID | midi_prog/prog55 -conn 9116 > /dev/null

cui_srv画面で

========================= cui_srv ==========================
|menu| / 0 |                                               |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|   adj_vol <0    >|                |   |
||                                                     |   |
  :
||                                                     |   |
||(save)  [tone_file         ]                         |   |
|+-----------------------------------------------------+   |
+----------------------------------------------------------+

ファイル名 tone_file で保存

struct tone_all_rec
tone
{
  name "strings"
  vco
    wave1 1
    wave2 1
    tune 12
    mix 0.500000
    ring 0
    alias_noise1 0
    alias_noise2 0
  fl1
    type 1
    freq 3000.000000
    Q 1.500000
  fl2
    type 0
    freq 0.000000
    Q 0.000000
  env
    attack 0.150000
    decay 0.500000
    sustain 0.800000
    release 0.500000
  level 1.000000
  lfo_modu
    pitch1 0
    pitch2 12
    filter1 0
    filter2 0
  lfo
    wave 0
    freq 4.000000
    delay 0.300000
  env_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  delay
    onoff 0
    sec 0.000000
    gain 0.000000
  chorus 1
,
  name "bass"
  vco
    wave1 1
    wave2 2
    tune 0
    mix 0.500000

  :

  delay
    onoff 0
    sec 0.000000
    gain 0.000000
  chorus 0
}
tones_lst
{
  prog 0
  note 36
  tone_compo
  {
    name "bass drum"
    note 28
    rate 1.000000
  ,
    name NULL
    note 0
    rate 0.000000
  }
,
  prog 0
  note 37
  tone_compo
  {
    name "side stick"
    note 110
    rate 1.000000
  ,
    name NULL
    note 0
    rate 0.000000
  }
,
  prog 0
  note 40
  tone_compo
  {
    name "snare"
    note 69

  :

  prog 48
  note -1
  tone_compo
  {
    name "strings"
    note -1
    rate 1.000000
  ,
    name NULL
    note 0
    rate 0.000000
  }
,
  prog 50
  note -1
  tone_compo
  {
    name "lead"

  :

  prog 0
  note 55
  tone_compo
  {
    name "cymbal"
    note 75
    rate 1.000000
  ,
    name NULL
    note 0
    rate 0.000000
  }
,
  prog -1
  note -1
  tone_compo NULL
}

なんか、非常にそれらしいです。

では、
一旦 ^C prog55の実行をとめて

例によって

$ (cd midi_prog ; make clean)
$ tar cf - midi_prog ; ssh <強力マシン> "cd <作業場所> rm -rf midi_prog ; tar xf - ; cd midi_prog ; make"

で、強力マシン上のprog55を作っておいて

$ cat L3007_06.MID | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog55 -conn <のろいマシン>:9116" > /dev/null

で、強力マシンのprog55起動して、のろいマシンのcui_srvに接続

cui_srvでは

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|   adj_vol <0    >|                |   |
||                                                     |   |
  :

で、新たに繋がった '1' のタブを選択して

========================= cui_srv ==========================
|menu| / 0 |/ 1 |                                          |
|====================== cui_tone =======================   |
||ch 0 | note <69 >|   adj_vol <0    >|                |   |
||                                                     |   |
  :
||                                                     |   |
||(save)  [tone_file2        ]                         |   |
|+-----------------------------------------------------+   |
+----------------------------------------------------------+

tone_file2 として保存してみます。

おお、強力マシンでは実行が速いので、素早くsaveしないと、
曲の波形の生成が終ってprog55が終了してしまうので注意です。

$ scp <強力マシン>:<作業場所>/tone_file2 .

と tone_file2 をのろいマシンにもってきて

$ diff -u tone_file tone_file2
$ 

のろいマシンで生成したファイルと比較して、一致
(当たり前だが、さすがわテキストファイル)


やっぱし最初からテキスト形式で (その3)

それでは続いてloadの処理

saveの逆をやればなんとかなるでしょうか?
ポインタの扱いがsaveより面倒なはずです。
mallocで領域を確保して、そこにloadせねばなりません。

構造体のポインタは配列を指してて末尾はNULLを含むというルールでsaveしてて、 要素数は特にsaveしてません。
なので、とりあえず末尾のデータが来るまでloadを繰り返して、 チェーンリストで保持しておいて、末尾まできたら改めて配列にまとめなおすべし。

この最後のまとめるところが、やばそうなので先に util.[ch] に追加してみます。

struct stklst{
	int size;
	void *p;
	struct stklst *prev;
};

struct stklst *
stklst_alloc(int size, struct stklst *prev)
{
	struct stklst *p;
	int sz = sizeof(*p) + size;

	if((p = malloc(sz)) == NULL) ERR("No Mem");
	p->size = size;
	p->p = p + 1;
	p->prev = prev;
	return p;
}

void
stklst_allfree(struct stklst *p)
{
	struct stklst *prev;
	for(; p; p=prev){
		prev = p->prev;
		free(p);
	}
}

int
stklst_allsize(struct stklst *p)
{
	int size = 0;
	for(; p; p=p->prev) size += p->size;
	return size;
}

static void *
arr_set(struct stklst *p, void *arr)
{
	if(p == NULL) return arr;
	arr = arr_set(p->prev, arr);
	memcpy(arr, p->p, p->size);
	return ((char*)arr) + p->size;
}

void *
stklst_to_arr(struct stklst *p)
{
	void *arr;

	if((arr = malloc(stklst_allsize(p))) == NULL) ERR("No Mem");
	arr_set(p, arr);
	stklst_allfree(p);
	return arr;
}

そうした上で rec.c に load関係を追加

prog55 から prog56 の差分です prog56.patch

コンパイルは通しましたが、未だ試してません...


やっぱし最初からテキスト形式で (その4)

それではデバッグ開始

$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
ERR memb_load() rec.c L372 : unexpect
$ 


tone.c
		fscanf(fp, "%c", &c1);
		if (c != '{') ERR("unexpect");
		skip1('\n', fp);

確かに間違ってます。
c1に読込み、cを見てました。
他の同様の箇所を含めて、修正。


$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
skip1() rec.c L291 :   (expect 
)
ERR skip1() rec.c L292 : unexpect
$ 

skip1(int c, FILE *fp)
{
	int r = fgetc(fp);
	if(r != c){
		char msg[64];
		sprintf(msg, "%c (expect %c)", r, c);
		MSG(msg);
		ERR("unexpect");
	}
}

改行を期待してて、そうじゃなかった様子。
これだけではなんなので、debugout追加

skip1(int c, FILE *fp)
{
	int r = fgetc(fp);
	fprintf(stderr, "skip1 %02x\n", c);
	if(r != c){
  :


$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
skip1 0a
skip1 20
skip1 0a
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 20
skip1 0a
skip1 0a
skip1 20
  :
skip1 20
skip1 0a
skip1 0a
skip1 20
skip1 0a
skip1() rec.c L292 :   (expect 
)
ERR skip1() rec.c L293 : unexpect
$ 

かなり進行してからのエラーの様子。

skip1()呼出箇所多数なので、マクロに変更して試してみます。

#if 1
#define skip1(c, fp) \
do{ \
	int r = fgetc(fp); \
	if(r != c){ \
		char msg[64]; \
		sprintf(msg, "%c (expect %c)", r, c); \
		MSG(msg); \
		ERR("unexpect"); \
	} \
}while(0)
#else
static void
skip1(int c, FILE *fp)
{
	int r = fgetc(fp);
	fprintf(stderr, "skip1 %02x\n", c);
	if(r != c){
		char msg[64];
		sprintf(msg, "%c (expect %c)", r, c);
		MSG(msg);
		ERR("unexpect");
	}
}
#endif


$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
memb_load() rec.c L415 :   (expect 
)
ERR memb_load() rec.c L415 : unexpect
$ 

L415とは

rec.c

void
memb_load(void *obj, struct memb *memb, FILE *fp)
{
  :
	memb_set_pointer(obj, memb, p);
	skip1('\n', fp);
}

この関数の末尾。
メンバがポインタで構造体以外の場合

deugout追加

	memb_set_pointer(obj, memb, p);
	fprintf(stderr, "%s %s\n", type, memb->name);
	skip1('\n', fp);
}

$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
char* name
char* name
char* name
char* name
char* name
memb_load() rec.c L416 :   (expect 
)
ERR memb_load() rec.c L416 : unexpect
$ 

char* の5つめ。

tone_file
 :
  name "bass drum"

!なるほど。空白ありの場合がダメ

		fscanf(fp, "\"%s\"", buf);
ここ
		fscanf(fp, "\"%[^\"]\"", buf);
修正

$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
memb_load() rec.c L416 : N (expect 
)
ERR memb_load() rec.c L416 : unexpect
$ 

12こめ。

tone_file
  :
}
tones_lst
{
  prog 0
  note 36
  tone_compo
  {
    name "bass drum"
    note 28
  :

tones_lst の先頭
その前に、

memb_load() rec.c L416 : N (expect 
)

なので、改行を期待してるのに 'N' が見えたはず。

tones_lst
{
  prog 0
  note 36
  tone_compo
  {
    name "bass drum"
    note 28
    rate 1.000000
  ,
    name NULL
    note 0
    rate 0.000000
  }
,

なので、NULLの方!
しまった、NULLの場合の処理を忘れてました。
追加


$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
ERR memb_load() rec.c L386 : unexpect
$ 

rec.c
	/* type is pointer */

	memset(strip_type, 0, sizeof(strip_type));
	memcpy(strip_type, type, strlen(type)-1);

	if(type_is_rec(strip_type)){
		struct rec *rec = rec_get(strip_type);
		struct stklst *sl = NULL;

		if((c = fgetc(fp)) != '\n'){
			fscanf(fp, "%s\n", buf);
			if(strcmp(buf, "NULL") != 0){
				MSG(buf);
				ERR("unexpect");
			}
			memb_set_pointer(obj, memb, NULL);
			return;
		}

		fscanf(fp, "%c", &c1);
		if (c1 != '{') ERR("unexpect"); // ここ
		skip1('\n', fp);

インデントの' '扱い抜けてる?
いや、fscanf %c は、最初にwhite space skipじゃなかった?
debugout追加

		fscanf(fp, "%c", &c1);
		fprintf(stderr, "c1=%c(%02x)\n", c1, c1);

$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
c1={(7b)
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
char* name
c1={(7b)
c1= (20)
ERR memb_load() rec.c L387 : unexpect
$ 

3回目で space
とりあえず、c1にspaceが入ってる。

tone_file

1つめ'{'は

struct tone_all_rec
tone
{

2つめと3つめ'{'は

tones_lst
{
  prog 0
  note 36
  tone_compo
  {

やはり、%c でspaceということ。
man scanf 確認すると、確かにspaceの読み飛ばしなしだった。
他の箇所含めて修正


$ (echo load ; cat tone_file L3007_06.MID ) | midi_prog/prog56 -srv 9117 -play
char* name
char* name
char* name
  :

OK.

loadできた様子。曲の再生がスタートしました。
debugout 外して、戻しておきます。

saveのとき に、
「出力テキストの先頭には、その根本の構造体の型名だけは必要です。
構造体に含まれる、それ以外の全ての型は、rec.c のしくみでたぐれるはずです。」
などと書いてました。

先頭の構造体の型名は、プログラムが知ってるので不要でしたね。
削除しておきます

save時にCUIで保存するファイルパスを指定してましたが、 ここはsave用の動作と割り切って、標準出力に出して終了するようにしておきます。
こうしておけば、強力マシンで試すときも、ローカルに保存したファイルを scpで取りにいかなくても済むはずです。
load時の様に、echo save で "save\n" が入力されたら、save動作としておきます。

CUIのスレッドも不要の場合、-nocui オプションでスレッドを起動しないようにしておきます。

tone_init()での初期化時に、tone_compoの末尾データの箇所で、 name NULLなら、toneにもNULLを設定しておく処理が抜けてました。
追加しておきます。

prog56 から prog57 の差分です prog57.patch

$ echo save | midi_prog/prog57 | tee tone_file3 | less
tone
{
  name "strings"
  vco
    wave1 1
    wave2 1
    tune 12
    mix 0.500000
    ring 0
    alias_noise1 0
    alias_noise2 0
  :

'G'

  :
  prog 0
  note 55
  tone_compo
  { 
    name "cymbal"
    note 75
    rate 1.000000
  , 
    name NULL
    note 0
    rate 0.000000
  }
, 
  prog -1
  note -1
  tone_compo NULL
}

'q'

$ 

$ diff -u tone_file tone_file3
--- tone_file 2015-04-16 01:00:00.000000000 +0900
+++ tone_file3 2015-04-18 01:00:00.000000000 +0900
@@ -1,4 +1,3 @@
-struct tone_all_rec
 tone
 {
   name "strings"
$ 

saveは大丈夫そうですね。

強力マシンで load を試してみます。

$ 

$ (cd midi_prog ; make clean)
$ tar cf - midi_prog | ssh <強力マシン> "cd <作業場所> ; rm -rf midi_prog ; tar xf - ; cd midi_prog ; make"
$ (echo load ; cat tone_file3 L3007_06.MID ) | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog57 -nocui" | play -t raw -r 44100 -c 2 -b 16 -s -

OK. 無事に再生

$ cp tone_file3 tone_file4
とコピーして tone_file4 を編集

304行目からのここ

  name "hi-hat close"
  vco
    wave1 2
    wave2 2
    tune 700
    mix 0.900000
    ring 1
    alias_noise1 1
    alias_noise2 1

alias_noise1,2 とも 1 から 0 に書き換えてみます。

    alias_noise1 0
    alias_noise2 0

$ (echo load ; cat tone_file4 L3007_06.MID ) | ssh <強力マシン> "cd <作業場所> ; midi_prog/prog57 -nocui -vol 9:3" | play -t raw -r 44100 -c 2 -b 16 -s -

書き換えた tone_file4 をロードして、ドラムの音を大きめで再生。

OK
ハイハットの音が、やかんを叩く音から牛乳瓶を叩く音に変わってます。


やっぱし最初からテキスト形式で (その5)

保存したファイルを見なおしてみると、プログラム番号やノート番号が判りにくいです。

テキストデータではGMの文字列で扱っておいて、後で変換するようにしてみます。
tone.h の struct tones_lst_rec に prog, noteの名前を追加しておいて、
tone.c にGMの表を追加します。
save/load時に、名前と整数との変換をすればよさそうです。
(あー、これでまたテキストファイルの形式が変わる T_T)

でも数値で指定したい場合もあるので、"50 String Ensemble 1" という風に出力するようにします。

prog57 から prog58 の差分です prog58.patch

$ echo save | midi_prog/prog58 > tone_file5

$ diff -u tone_file3 tone_file5 | head
--- tone_file3	2015-04-18 00:00:00.000000000 +0900
+++ tone_file5	2015-04-19 00:00:00.000000000 +0900
@@ -518,8 +518,8 @@
 }
 tones_lst
 {
-  prog 0
-  note 36
+  prog_name "0 PROG_DRUM"
+  note_name "36 Bass Drum 1"
$ 

確かに変わってて

$ (echo load ; cat tone_file5 ; echo save) | midi_prog/prog58 > tone_file6

$ diff -u tone_file5 tone_file6
$ 

loadしなおして、saveしなおしても特に変わりなし
OK


ボコーダー

高校生の頃、梅田のとある楽器店での事。
確かローランドのJunoというシンセサイザーが、中身むき出しになってました。 本体からケーブルが出て、外の基板につながっていて、基板にマイクが。

これはボコーダーでは!?
鍵盤を押して、マイクに向かって「あー、うー、トキオー」
なんかそれらしい音が鳴ってたようです。

ということで、これまで作ってきたプログラムでも、音声を入力して喋らせてみます。

Wikipediaによると、 バンドパスフィルタで、音声の母音の特徴を抽出しておいて、 楽器側では、VCFで変調をかければ何とかなりそうです。

ではまず上流の、音声から母音の特徴を抽出する事から試してみます。

とりあえず「フォルマント」で調べてみると、 F1(500から1000Hz), F2(1500から3000Hz) が重要らしいです。
この領域で複数のバンドを設けて、バンドパス・フィルタで分解すれば良さそうです。
バンドの数は多いほど、音声らしく再生できるそうな。
といっても、今のプログラムでは、MIDIチャンネル数16が上限です。
F1, F2 に 8チャンネルづつ割り当てるとすると

f.c

#include <stdio.h>

int
main(void)
{
	int N=16, H=N/2, i;
	for(i=0; i<N; i++){
		double f = i < H ?
			500 + (1000.0 - 500) * i / (H-1) :
			1500 + (3000.0 - 1500) * (i-H) / (H-1);
		printf("%d %f\n", i, f);
	}
}

$ gcc -o f f.c
$ f
0 500.000000
1 571.428571
2 642.857143
3 714.285714
4 785.714286
5 857.142857
6 928.571429
7 1000.000000
8 1500.000000
9 1714.285714
10 1928.571429
11 2142.857143
12 2357.142857
13 2571.428571
14 2785.714286
15 3000.000000
$ 

音声データを入力として、この16個バンドでフィルタを掛けて分解するプログラムを作ってみます。
フィルタは、midi_prog の filter.[ch] を、波形の出力には out.[ch] をそのまま使えそうです。
音声の入力部分は、これまでなかったので新たにin.[ch]として追加します。
のパラメータは、out側のものを流用しておいて、モノラルで扱うことにします。
1つの入力に対して16個の出力なので、とりあえず16個のファイルに波形データを出してみます。

とりあえず一発目としてBPFかけてみるだけの差分です vcd1.patch

さて試すためには音声入力をどうしよう?
通勤のおとものMP3プレーヤーのSDカードに、ファイルが転がってます。
日曜日の昼下がりのFMラジオ。 敬愛する山下達郎さんの番組「サンデーソングブック」を録音したファイルがありました。
このオープニグ・トークの部分を拝借してみます。

t-140216.MP3 ファイルサイズ 81754363 (81M)

2014年2月16日の放送分

MP3プレーヤーで再生してみると、先頭から3分11秒のあたりで、
山下達郎大先生の声が入ります。

ここから3分間程切り出してみます。

とりあえずMP3形式からwav形式に変換します。

$ lame --decode t-140126.MP3 t-140216.wav
input:  t-140126.MP3  (44.1 kHz, 2 channels, MPEG-1 Layer III)
output: t-140216.wav  (16 bit, Microsoft WAVE)
nskipping initial 529 samples (encoder+decoder delay)
hip: Can't step back 333 bytes!
Frame#  9176/130404 192 kbps                 

表示から
- サンプリング周波数 44100 Hz
- 2 channel
- 16 bit

.wav ファイルのサイズは 600890344 バイトでした

soxのplayコマンドで、ちょっと再生してみます。

$ play t-140216.wav
 :
t-140126.wav:

 File Size: 601M      Bit Rate: 1.41M
  Encoding: Signed PCM    
  Channels: 2 @ 16-bit   
Samplerate: 44100Hz      
 :

データはsignedで入ってますね。
エンディアンは情報ないですが...
となるときっとリトルエンディアンでしょう ;-p)

soxコマンドでモノラルのraw形式に変換して、
ddコマンドで切り出してみます。

モノラル16 bitなので、1サンプルあたり2バイト
44100 Hz で 3分11秒の位置は、

2*44100*(3*60+11) バイト目
ddコマンドで1ブロック4Kバイトで扱うとなると

2*44100*(3*60+11)/4096 = 4112.84179687500000000000
4112ブロック

3分間のデータサイズは
2*44100*3*60 バイト

4Kのブロックに換算すると

2*44100*3*60/4096 = 3875.97656250000000000000
3876ブロック分になります。

$ sox t140216.wav -t raw -c 1 - | dd bs=4096 skip=4112 count=3876 > a.raw
3876+0 records in
3876+0 records out
15876096 bytes (16 MB) copied, 1.44943 s, 11.0 MB/s

a.rawのサイズは16Mバイト程度

再生して確認してみます
  
$ play -t raw -r 44100 -c 1 -b 16 -s a.raw

「みなさんこんにちは。ご機嫌いかがでしょうか? 山下達郎です」

セリフ頭出し OKです。

では、さっそくフィルターをかけてみます。

$ (cd midi_prog ; make)

$ cat a.raw | midi_prog/vcd -r 44100 -b 16 -s

ファイル
test-0 から test-15 まで生成されました。
  
$ play -t raw -r 44100 -c 1 -b 16 -s test-0

$ play -t raw -r 44100 -c 1 -b 16 -s test-8

$ play -t raw -r 44100 -c 1 -b 16 -s test-15

などと再生してみると、うーむ。
確かにイコライザーをかけたように、違いが出てます。

が、こんなもんで大丈夫なんだろうか?
もっとパラメータのQを大きくして、鋭く尖らせた方がいいのかな?

まぁ、一旦この程度で試してみます。


ボコーダー (その2)

続いて、BPFをかけた波形から音量の変化を取り出して、 MIDIのチャンネル・ボリュームのイベントを生成してみます。

MIDIのチャンネル・ボリュームのイベントで、音量を調節しますが、 イベントのデルタタイムの単位より短い調整はできません。

なのでBPF後の波形に対して、デルタタイム1つ分の期間の音量として、 波形の絶対値を積算して、個数で割って平均をとればよさそうです。
デルタタイム1つ分の期間とは?
例えば、これまでつかってきたMIDIファイルの設定を見てみます。

$ gcc -o prog_onoff9 prog_onoff9.c
$ cat L3007_06.MID | ./prog_onoff9 | head
header
id='MThd'
size=6
format type=0
track num=1
time division=96 (0x60)

track
id='MTrk'
size=94001
$ 

四分音符1つあたりを、96分割する設定になってて

main.c
			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;

メタイベントの 0x51 = 81 が テンポの設定で

$ cat L3007_06.MID | ./prog_onoff9 | grep 'meta.*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 
$ 

このあたりの説明 によると、

7*256*256/1000000 = 0.458752
8*256*256/1000000 = 0.524288
10*256*256/1000000 = 0.65536

なので、四分音符一つあたり 0.5秒程度

main.c
 :
	tempo = 500000;
 :

としてるので、set tempoのメタイベントが無ければ、
デフォルトで0.5秒に設定してました。

という事で、とりあえずデルタタイム1あたりは、
0.5秒 / 96分割 で進めてみます。

MIDIチャンネル・ボリュームのイベントは、とりあえずMSBだけで。

main.c
		case 0xb: /* control change */
			type = rd();
			v = rd();
			switch(type){
			case 7: /* channel volume msb */
				msb_set(&ch_inf[ch].vol, v);
				break;

なので、コントロールチェンジ・イベントのタイプ7で、0から127の値でしたね。

イベント出力については、 cui_midi.c で色々やってたはずです。
使えそうな関数を、midi_prog/wrt.[ch] として取り込んでおきます。

鍵盤ONのイベントは、とりあえず 440Hz の「ラ」 ノート番号 69 だけ押してみます。

第二弾として、MIDIのイベントを出力するようにしたパッチです vcd2.patch

$ (cd midi_prog ; make)
$ gcc -o prog_onoff9 prog_onoff9.c

$ cat a.raw | midi_prog/vcd | ./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 ctl chg ch=0 type=7 v=0
sec=0.000 prog num ch=0 v=0
sec=0.000 on ch=0 note=69 velo=127
sec=0.000 ctl chg ch=1 type=7 v=0
sec=0.000 prog num ch=1 v=1
sec=0.000 on ch=1 note=69 velo=127
sec=0.000 ctl chg ch=2 type=7 v=0
sec=0.000 prog num ch=2 v=2
sec=0.000 on ch=2 note=69 velo=127
sec=0.000 ctl chg ch=3 type=7 v=0
sec=0.000 prog num ch=3 v=3
sec=0.000 on ch=3 note=69 velo=127
sec=0.000 ctl chg ch=4 type=7 v=0
sec=0.000 prog num ch=4 v=4
sec=0.000 on ch=4 note=69 velo=127
sec=0.000 ctl chg ch=5 type=7 v=0
sec=0.000 prog num ch=5 v=5
sec=0.000 on ch=5 note=69 velo=127
sec=0.000 ctl chg ch=6 type=7 v=0
sec=0.000 prog num ch=6 v=6
sec=0.000 on ch=6 note=69 velo=127
sec=0.000 ctl chg ch=7 type=7 v=0
sec=0.000 prog num ch=7 v=7
sec=0.000 on ch=7 note=69 velo=127
sec=0.000 ctl chg ch=8 type=7 v=0
sec=0.000 prog num ch=8 v=8
sec=0.000 on ch=8 note=69 velo=127
sec=0.000 ctl chg ch=9 type=7 v=0
sec=0.000 prog num ch=9 v=9
sec=0.000 on ch=9 note=69 velo=127
sec=0.000 ctl chg ch=10 type=7 v=0
sec=0.000 prog num ch=10 v=10
sec=0.000 on ch=10 note=69 velo=127
sec=0.000 ctl chg ch=11 type=7 v=0
sec=0.000 prog num ch=11 v=11
sec=0.000 on ch=11 note=69 velo=127
sec=0.000 ctl chg ch=12 type=7 v=0
sec=0.000 prog num ch=12 v=12
sec=0.000 on ch=12 note=69 velo=127
sec=0.000 ctl chg ch=13 type=7 v=0
sec=0.000 prog num ch=13 v=13
sec=0.000 on ch=13 note=69 velo=127
sec=0.000 ctl chg ch=14 type=7 v=0
sec=0.000 prog num ch=14 v=14
sec=0.000 on ch=14 note=69 velo=127
sec=0.000 ctl chg ch=15 type=7 v=0
sec=0.000 prog num ch=15 v=15
sec=0.000 on ch=15 note=69 velo=127
sec=0.005 ctl chg ch=0 type=7 v=14
sec=0.005 ctl chg ch=1 type=7 v=14
sec=0.005 ctl chg ch=2 type=7 v=14
sec=0.005 ctl chg ch=3 type=7 v=13
sec=0.005 ctl chg ch=4 type=7 v=12
sec=0.005 ctl chg ch=5 type=7 v=12
  :

とりあえずイベントは出てます

'G'
  :
sec=179.995 ctl chg ch=2 type=7 v=6
sec=179.995 ctl chg ch=3 type=7 v=6
sec=179.995 ctl chg ch=4 type=7 v=6
sec=179.995 ctl chg ch=7 type=7 v=5
sec=179.995 ctl chg ch=10 type=7 v=3
sec=179.995 ctl chg ch=11 type=7 v=3
sec=180.000 ctl chg ch=0 type=7 v=13
sec=180.000 ctl chg ch=1 type=7 v=11
sec=180.000 ctl chg ch=2 type=7 v=10
sec=180.000 ctl chg ch=3 type=7 v=9
sec=180.000 ctl chg ch=4 type=7 v=8
sec=180.000 ctl chg ch=5 type=7 v=7
sec=180.000 ctl chg ch=6 type=7 v=7
sec=180.000 ctl chg ch=7 type=7 v=7
sec=180.000 ctl chg ch=8 type=7 v=5
sec=180.000 ctl chg ch=10 type=7 v=4
sec=180.000 ctl chg ch=11 type=7 v=4
sec=180.000 ctl chg ch=12 type=7 v=4

きっかり3分で終了と


ボコーダー (その3)

次は後段の楽器側

といっても、BPFでバンドを強調した音色を用意して、 設定するだけでいいはずです。

強調する周波数は、最初に表示してみた値

0 500.000000
1 571.428571
2 642.857143
3 714.285714
4 785.714286
5 857.142857
6 928.571429
7 1000.000000
8 1500.000000
9 1714.285714
10 1928.571429
11 2142.857143
12 2357.142857
13 2571.428571
14 2785.714286
15 3000.000000

BPFのパラメータQは、vcd.c 側のデフォルト1.0に合わせてみます。

キャリアとしては倍音が多い波形がいいので、とりあえずノコギリ波で。
エンベロープの設定は、チャンネルボリュームのイベントが制御するから、 邪魔をしないように、単純なON/OFFで。

wave.h

#define WAVE_SIN	0
#define WAVE_SAW	1
#define WAVE_SQUARE	2
#define WAVE_NOISE	3

なのでノコギリの値は 1 です


filter.h

#define LPF		1
#define HPF		2
#define BPF		3

なので、BPF は 3 です


以前にsaveした tone_file5 をみると

tone
{
  name "strings"
  vco
    wave1 1
    wave2 1
    tune 12
    mix 0.500000
    ring 0
    alias_noise1 0
    alias_noise2 0
  fl1
    type 1
    freq 3000.000000
    Q 1.500000
  fl2
    type 0
    freq 0.000000
    Q 0.000000
  env
    attack 0.150000
    decay 0.500000
    sustain 0.800000
    release 0.500000
  level 1.000000
  lfo_modu
    pitch1 0
    pitch2 12
    filter1 0
    filter2 0
  lfo
    wave 0
    freq 4.000000
    delay 0.300000
  env_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  delay
    onoff 0
    sec 0.000000
    gain 0.000000
  chorus 1
,
  :

なので、これを元に

tone
{
  name "vocoder0"
  vco
    wave1 1
    wave2 1
    tune 0
    mix 0
    ring 0
    alias_noise1 0
    alias_noise2 0
  fl1
    type 1
    freq 500
    Q 1
  fl2
    type 0
    freq 0
    Q 0
  env
    attack 0
    decay 0
    sustain 1
    release 0
  level 1
  lfo_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  lfo
    wave 0
    freq 4
    delay 0
  env_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  delay
    onoff 0
    sec 0
    gain 0
  chorus 0
,

あとは、コピーペーストで16個用意し、
名前の数字を変えて、filter の freq をテーブルの値に設定します。

tone_file5
  :
}
tones_lst
{
  prog_name "0 PROG_DRUM"
  note_name "36 Bass Drum 1"
  tone_compo
  {
    name "bass drum"
    note 28
    rate 1.000000
  ,
    name NULL
    note 0
    rate 0.000000
  }
,

の tones_lst の設定は、
プログラム番号 0 から 15 の 16個だけ用意すればいいので

tones_lst
{
  prog_name "0"
  note_name "-1 any"
  tone_compo
  {
    name "vocoder0"
    note -1
    rate 1
  ,
    name NULL
    note 0
    rate 0
  }
,

これをコピーして、名前の数字を変更していきます。

作成した音色のファイルです tone_file_vcd

vcd用の音色をloadして、音声データを変換して生成したMIDIデータを与えて、波形データを生成させます

$ ((echo load ; cat tone_file_vcd) ; (cat a.raw | midi_prog/vcd)) | midi_prog/prog58 -nocui | hd | head
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000390  00 00 00 00 00 00 00 00  a8 01 a8 01 91 01 91 01  |................|
000003a0  7d 01 7d 01 6f 01 6f 01  66 01 66 01 5d 01 5d 01  |}.}.o.o.f.f.].].|
000003b0  48 01 48 01 27 01 27 01  fe 00 fe 00 d3 00 d3 00  |H.H.'.'.........|
000003c0  ab 00 ab 00 88 00 88 00  6c 00 6c 00 56 00 56 00  |........l.l.V.V.|
000003d0  46 00 46 00 3c 00 3c 00  36 00 36 00 34 00 34 00  |F.F.<.<.6.6.4.4.|
000003e0  35 00 35 00 37 00 37 00  3c 00 3c 00 3f 00 3f 00  |5.5.7.7.<.<.?.?.|
000003f0  37 00 37 00 1f 00 1f 00  fa ff fa ff cf ff cf ff  |7.7.............|
00000400  a5 ff a5 ff 7e ff 7e ff  5e ff 5e ff 45 ff 45 ff  |....~.~.^.^.E.E.|

何やら波形でてます。

さっそく、playコマンドに接続して再生

$ ((echo load ; cat tone_file_vcd) ; (cat a.raw | midi_prog/vcd)) | midi_prog/prog58 -nocui -play

ちょっとボリュームが小さいな

$ ((echo load ; cat tone_file_vcd) ; (cat a.raw | midi_prog/vcd)) | midi_prog/prog58 -nocui -play -vol all:5

なーんか、モールス信号みたい...
これじゃない感がただよってます。

パラメータQ を 1 から 10 に変えてみます

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 10/') ; (cat a.raw | midi_prog/vcd -Q 10)) | \
midi_prog/prog58 -nocui -play -vol all:5

あんまり変わらんような...

では 100 に

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 100/') ; (cat a.raw | midi_prog/vcd -Q 100)) | \
midi_prog/prog58 -nocui -play

なんかモガモガ言ってるような?

そもそもキャリア440HzがF1ぎりぎりだから?
1オクターブ下げて 69-12 = 57 を指定してみます。

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 100/') ; (cat a.raw | midi_prog/vcd -Q 100 -note 57)) | \
midi_prog/prog58 -nocui -play

なーんか、違うような...

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 400/') ; (cat a.raw | midi_prog/vcd -Q 400 -note 57)) | \
midi_prog/prog58 -nocui -c 1 -vol all:0.3 -play

やっぱり違うような...

キャリアの音色変えて試してみます。 ノコギリ波をノイズに変更してみます。

wave.h

#define WAVE_NOISE	3

なので

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 400/' -e 's/wave1 1/wave1 3/') ; (cat a.raw | midi_prog/vcd -Q 400 -note 57)) | \
midi_prog/prog58 -nocui -c 1 -vol all:0.3 -play

なにか金属っぽい感じでゴロゴロ言ってます

Qをいじってみると

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 20/' -e 's/wave1 1/wave1 3/') ; (cat a.raw | midi_prog/vcd -Q 20 -note 57)) | \
midi_prog/prog58 -nocui -c 1 -vol all:2 -play

おお! これで、なんとか聞きとり可能です。
キャリアがノイズなので、しわがれ声でゴニョゴニョしゃべってます。
達郎先生ごめんなさい ;-p)

Qは20が良さそうで、ノコギリ波ではキャリアとして倍音が足りてなかったでしょうか?
一度ノイズで慣れておくと、ノコギリ波や、矩形波でも、なんとか聞こえてくる気がします。(^_^;
鍵盤一本弾きしか指定できないので、和音を指定できるようにしてみます。

-note オプションで複数指定可能にしたパッチです vcd3.patch

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 20/') ; (cat a.raw | midi_prog/vcd -Q 20 -note 45,52,57,61,64,69)) | \
midi_prog/prog58 -nocui -c 1 -play

だみだー。
のろいマシンで処理がおっつかんです。

ここは一つ強力マシンのお力で

$ (cd midi_prog ; make clean)
$ tar cf - midi_prog | ssh <強力マシン> "cd <作業場所> ; tar xf - ; cd midi_prog ; make"
$ (cd midi_prog ; make)

$ ((echo load ; cat tone_file_vcd | sed -e 's/Q 1/Q 20/') ; (cat a.raw | midi_prog/vcd -Q 20 -note 45,52,57,61,64,69)) | \
ssh <強力マシン> "cd <作業場所> ; midi_prog/prog58 -nocui -c 1 -vol all:0.3" | play -t raw -r 44100 -b 16 -c 1 -s -

おおぅ、ずいぶんいい改善されました。v^_^)


ボコーダー (その4)

まずは ボコーダー (その3) で大きな過ちがありました。

|filter.h
|
|#define LPF		1
|#define HPF		2
|#define BPF		3
|
|なので、BPF は 3 です

としながら

|tone
|{
|  name "vocoder0"
|  vco
|    wave1 1
|    wave2 1
|    tune 0
|    mix 0
|    ring 0
|    alias_noise1 0
|    alias_noise2 0
|  fl1
|    type 1
|    freq 500
|    Q 1

「type 1」!
正しくは「type 3」です

Qの値もどうやら20で良さそうなので、修正して試してみます。

$ sed -e 's/Q 1/Q 20/' -e 's/type 1/type 3/' tone_file_vcd > tf_vcd.txt

達郎先生の音声データも、もう少し絞りこんでおきます。
a.raw の先頭から2.5秒のところから、30秒を切り出しておきます。

2*44100*2.5/4096 
53.83300781250000000000
54 block

2*44100*30/4096 = 645.99609375000000000000
646 block

$ cat a.raw | dd bs=4096 skip=54 count=646 > voice.raw
646+0 records in
646+0 records out
2646016 bytes (2.6 MB) copied, 0.242224 s, 10.9 MB/s

サンプリング周波数 44100Hz
モノラル 16 bit だから、2バイト/サンプルで 30秒
44100*30*2 = 2646000
確かに 2.6Mバイト程度

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -Q 20 -note 32)) | \
midi_prog/prog58 -nocui -c 1 -sox a.wav

$ play a.wav

「これじゃない」感が、「これかも?」感に?

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -Q 20 -note 32,39)) | \
midi_prog/prog58 -nocui -c 1 -sox a2.wav

$ play a2.wav

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -Q 20 -note 32,39,44)) | \
midi_prog/prog58 -nocui -c 1 -sox a3.wav

$ play a3.wav

それでも、なにやら変なノイズが?
ひょっとして、音声にBPFかけたあと、音量を取得するために
振幅の絶対値を積算して、平均の値を求めてます。

音量のイベント出力は、デルタタイムの単位0.5秒/96分割なので、96x2のサンプリング周波数
ということは96Hzより下の成分がノイズとして聞こえてるのかも?
人の可聴範囲は20Hzからと言われてるので、
ローバスフィルタで、20Hzより上は消せばいいのかも知れません。

試してみます。

音量の抽出にLPFを追加したパッチです vcd4.patch

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -note 32)) | \
midi_prog/prog58 -nocui -c 1 -sox b.wav

$ play b.wav

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -note 32,39)) | \
midi_prog/prog58 -nocui -c 1 -sox b2.wav

$ play b2.wav

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -note 32,39,44)) | \
midi_prog/prog58 -nocui -c 1 -sox b3.wav

$ play b3.wav

-lpf_Q は 0.8 だと、途中から発振したようなノイズが入ってきます。

$ ((echo load ; cat tf_vcd.txt ) ; (cat voice.raw | midi_prog/vcd -lpf_Q 0.7 -note 32,39,44)) | \
midi_prog/prog58 -nocui -c 1 -sox c3.wav

$ play c3.wav

-lpf_Q は 0.7 くらいだと、ノイズも入ってこず、いい感じです。

1/sqrt(2) が 0.7 くらい
調べてみると Q = 1/sqrt(2) とは、
「固有振動数 = カットオフ周波数」となる「バタワース特性」と呼ばれるらしいです。


ボコーダー (その5)

ここでひとつ「大返し」を

ここまで各MIDIチャンネルに、BPFの異る音色を割当てて、チャネルボリュームで制御してきました。
いやいや、素直に音を出力してそれをキャリア入力として使えば、それでいいのでは?
midi_prog/prog58 の出力側に、ボコーダプログラムを繋いで、そこでBPFかけて別口の音声にもBFPかけて、制御したらいいのでは?

なんかMIDIと関係なくなって、身もフタも無い気もしますが...
「大返し」試してみます

身もフタも無いパッチです voco.patch

とりあえず、試せるようにするために、キャリアの入力指定が無い場合は、 vcoの簡単な部分だけ内蔵しておいて、内部でキャリアを生成するようにしてみました。

出力形式として指定するパラメータは、これまでのmidi_prog/prog58でお馴染みの次の形式にして、 キャリアも音声も、同じ形式のものを入力する前提にしてます。

$ cat voice.raw | midi_prog/voco -voice - -c 1 | play -t raw -r 44100 -c 1 -b 16 -s -

いやいや、これで十分では?
内蔵のキャリア生成で、ものすごくボコーダらしく聞こえます (>_<)

$ cat voice.raw | midi_prog/voco -voice - -c 1 | sox -t raw -r 44100 -c 1 -b 16 -s - voco.wav
$ lame voco.wav voco.mp3

$ cat voice.raw | midi_prog/voco -c 1 -voice - -lpf_freq 100 -vco_f 40 -tune 3 | sox -t raw -r 44100 -c 1 -b 16 -s - jet.wav
$ lame jet.wav jet.mp3

勝手に加工しといてなんですが、オリジナルの達郎先生のお声とは、随分印象が違います
voco.mp3
jet.mp3


MIDIファイルのテキスト変換

ここで、なるべくMIDIに関係のある事にゆり戻しておきます。
SMFの中身をテキストに変換したり、テキストからSMFに戻してみたりしてみます。

ちょっと中身を表示するのは既に、 prog_onoff9.c でやってました。

データ部分をはしょらずに全部変換しておいて、逆の変換で完全に元に戻せるようにします。

コマンド名は midtxt で。

midtxt.patch

$ cat L3007_06.MID | midi_prog/midtxt > a.txt

$ head a.txt
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=94001
delta=0 meta type=3 n=15 54 68 65 20 45 6e 64 20 6f 66 20 41 73 69 61
delta=0 meta type=2 n=26 28 43 29 31 39 39 33 20 52 6f 6c 61 6e 64 20 43 6f 72 70 6f 72 61 74 69 6f 6e
delta=0 meta type=88 n=4 04 02 18 08
$ 

$ tail a.txt
delta=2 off ch=9 note=40 velo=64
delta=2 on ch=9 note=40 velo=102
delta=2 off ch=5 note=91 velo=64
delta=0 off ch=5 note=86 velo=64
delta=0 off ch=9 note=40 velo=64
delta=2 off ch=1 note=31 velo=64
delta=0 off ch=2 note=91 velo=64
delta=0 off ch=2 note=86 velo=64
delta=17 off ch=8 note=31 velo=64
delta=749 meta type=47 n=0
$ 

次に、生成したテキスト a.txt から バイナリへ変換

$ cat a.txt | midi_prog/midtxt -r > a.mid

$ cmp L3007_06.MID a.mid
$ 

一致 OK


ボコーダー (その6)

また振子がボコーダーに戻ります。

SMFのテキスト変換で、キャリア用のSMFが作り易くなったので、 ボコーダー内蔵のVCOじゃなくて、 midi_prog/prog58 で生成した波形で試してみます。

まずはMIDIテキストを作ってみます

$ cat L3007_06.MID | midi_prog/midtxt | head -n 7 | tee b.txt
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=94001
$ 

b.txtを編集して次の内容に更新します

$ cat b.txt
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=0
delta=0 prog_num ch=0 v=50
delta=0 ctl_chg ch_vol_msb ch=0 v=100
delta=0 on ch=0 note=33 velo=100
delta=7680 off ch=0 note=33 velo=100
$ 

trksz は prog58 側では無視してるので、とりあえず 0
MIDIチャンネル 0 にプログラム番号50 (String Ensemble 2) をセット
MIDIチャンネル 0 のボリュームを 100 にセット
MIDIチャンネル 0 の鍵盤 33 を 速度 100 で ON 
(33 は 69-12*3 で、440Hzの3オクターブ下なので 440/(2^3) = 55 Hz)
デルタタイム7680後に、鍵盤 33 を OFF
(デルタタイム1は四分音符96分割した時間で、
 デフォルトは60秒で四分音符は120個のテンポ
 96*2*40 = 7680
 で、40秒鍵盤を押した状態にしてます)


鳴らしてみます。

$ cat b.txt | midi_prog/midtxt -r | midi_prog/prog58 -nocui -c 1 -play

「ビュイ〜〜〜」
と重低音が鳴って

In:0.00% 00:00:40.00 [00:00:00.00] Out:1.92M [      |      ]        Clip:0
Done.

40秒で止まりました。
OK

ではボコーダに接続

$ cat b.txt | midi_prog/midtxt -r | midi_prog/prog58 -nocui -c 1 \
| midi_prog/voco -carrier - -voice voice.raw -c 1 \
| play -t raw -r 44100 -c 1 -b 16 -s -

ちょっと音が小さい
キャリアのボリューム8倍に

$ cat b.txt | midi_prog/midtxt -r | midi_prog/prog58 -nocui -c 1 -vol all:8 \
| midi_prog/voco -carrier - -voice voice.raw -c 1 \
| play -t raw -r 44100 -c 1 -b 16 -s -

こんな感じです

$ cat b.txt | midi_prog/midtxt -r | midi_prog/prog58 -nocui -c 1 -vol all:8 \
| midi_prog/voco -carrier - -voice voice.raw -c 1 \
| sox -t raw -r 44100 -c 1 -b 16 -s - voco2.wav

$ lame voco2.wav voco2.mp3

voco2.mp3

キャリアの音をもちょっと豪華にしてみます
ここまで -c 1 で試してましたが、デフォルト設定の -c 2 でも試してみます

まず達郎先生の音声をとりあえず -c 2 にしておきます

$ cat voice.raw | sox -t raw -r 44100 -b 16 -c 1 -s - voice.wav

一旦 voice.wav にしてから 

$ sox voice.wav -t raw -c 2 voice2.raw

これで左右に同じ音声

$ cat voice2.raw | play -t raw -r 44100 -c 2 -b 16 -s -

確かに -c 2 としてちゃんと再生できてます

おつぎはキャリアのMIDIデータ

$ cp b.txt b2.txt

b2.txt としてcopyして b2.txt を編集します

$ cat b2.txt
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=0
delta=0 prog_num ch=0 v=50
delta=0 ctl_chg ch_vol_msb ch=0 v=100
delta=0 ctl_chg pan_msb ch=0 v=48
delta=0 on ch=0 note=33 velo=100
delta=0 on ch=0 note=40 velo=100
delta=0 on ch=0 note=45 velo=100
delta=0 prog_num ch=1 v=81
delta=0 ctl_chg ch_vol_msb ch=1 v=100
delta=0 ctl_chg pan_msb ch=0 v=80
delta=0 on ch=1 note=57 velo=100
delta=0 on ch=1 note=64 velo=100
delta=0 on ch=1 note=69 velo=100
delta=7680 off ch=0 note=33 velo=100
delta=7680 off ch=0 note=40 velo=100
delta=7680 off ch=0 note=45 velo=100
delta=7680 off ch=1 note=57 velo=100
delta=7680 off ch=1 note=64 velo=100
delta=7680 off ch=1 note=69 velo=100
$ 

MIDIチャンネル0の音を3音の和音にして、PANで少し左に振っておきます
64-16=48

MIDIチャンネル1に、プログラム番号81 (Lead 1 (square)) をセットして
2オクターブ上の鍵盤の和音に
PANで少し右に振っておきます
64+16=80

$ cat b2.txt | midi_prog/midtxt -r | midi_prog/prog58 -nocui -play

処理が重く under-run 出まくりです (>_<)

とりあえずファイルに保存

$ cat b2.txt | midi_prog/midtxt -r | midi_prog/prog58 -nocui -vol all:4 > b2-carrier.raw

b2-carrier.raw ファイルサイズ 42336000

再生して確認

$ cat b2-carrier.raw | play -t raw -r 44100 -c 2 -b 16 -s -

左右から「ビュオワ〜〜〜」

$ cat b2-carrier.raw | midi_prog/voco -carrier - -voice voice2.raw \
| sox -t raw -r 44100 -c 2 -b 16 -s - voco3.wav

ボコーダに喰わせてみて、ファイルに落して再生

$ play voco3.wav

左右に振ってみたものの、なんか、あんまり凄そうでもない感じでした。
所詮キャリアでしかないのかな?

$ lame voco3.wav voco3.mp3

voco3.mp3


純正律

無闇に素人が手を出してはいけない領域という気もしますが、 恐れずチャレンジしてみます。

平均律と純正律とは?

ここまでプログラムで取り扱ってきた音階は、平均律です。
MIDIイベントのノート番号(鍵盤の位置)と、音の周波数の関係は ノート番号と周波数の関係 で説明してました。

平均律で換算するので
周波数 = 440 × 2^( (ノート番号 - 69) / 12 )

このとき既に「平均律」ゆーてましたね。

つまり何が平均か?

周波数の対数が、均等になるように並べたという事ですね。

では、ある鍵盤の位置の周波数を1として、1オクターブ分の周波数を見てみると。

$ for i in $(seq 0 11) ; do echo -n "$i " ; echo "e( $i/12 * l(2) )" | bc -l ; done
0 1.00000000000000000000
1 1.05946309435929526455
2 1.12246204830937298142
3 1.18920711500272106671
4 1.25992104989487316475
5 1.33483985417003436481
6 1.41421356237309504878
7 1.49830707687668149878
8 1.58740105196819947472
9 1.68179283050742908604
10 1.78179743628067860946
11 1.88774862536338699324
$

2^x の計算は底を2からeに変換して exp( x * ln(2) ) としてます

1オクターブ上は

$ for i in $(seq 0 11) ; do echo -n "$i " ; echo "2 * e( $i/12 * l(2) )" | bc -l ; done
0 2.00000000000000000000	;;; 1オクターブ上なので2倍
1 2.11892618871859052910
2 2.24492409661874596284
3 2.37841423000544213342
4 2.51984209978974632950
5 2.66967970834006872962
6 2.82842712474619009756
7 2.99661415375336299756	;;; ここが3倍に近いが、ちょっと低い
8 3.17480210393639894944
9 3.36358566101485817208
10 3.56359487256135721892
11 3.77549725072677398648
$ 

さらに1オクターブ上は

$ for i in $(seq 0 11) ; do echo -n "$i " ; echo "4 * e( $i/12 * l(2) )" | bc -l ; done
0 4.00000000000000000000	;;; 4倍
1 4.23785237743718105820
2 4.48984819323749192568
3 4.75682846001088426684
4 5.03968419957949265900	;;; 5倍に近いが、ちょっと高い
5 5.33935941668013745924
6 5.65685424949238019512
7 5.99322830750672599512	;;; 先の3倍弱の2倍で、6倍よりちょっと低い
8 6.34960420787279789888
9 6.72717132202971634416
10 7.12718974512271443784
11 7.55099450145354797296
$ 

さらに倍

$ for i in $(seq 0 11) ; do echo -n "$i " ; echo "8 * e( $i/12 * l(2) )" | bc -l ; done
0 8.00000000000000000000	;;; 8倍
1 8.47570475487436211640
2 8.97969638647498385136	;;; 9倍弱
3 9.51365692002176853368
4 10.07936839915898531800	;;; 10倍強
5 10.67871883336027491848
6 11.31370849898476039024
7 11.98645661501345199024	;;; 12倍弱
8 12.69920841574559579776
9 13.45434264405943268832
10 14.25437949024542887568
11 15.10198900290709594592	;;; 15倍強
$ 

基準の周波数を3にしてみると

$ for i in $(seq 0 11) ; do echo -n "$i " ; echo "3 * e( $i/12 * l(2) )" | bc -l ; done
0 3.00000000000000000000	;;; 3倍
1 3.17838928307788579365
2 3.36738614492811894426
3 3.56762134500816320013
4 3.77976314968461949425
5 4.00451956251010309443	;;; 4倍強
6 4.24264068711928514634
7 4.49492123063004449634
8 4.76220315590459842416
9 5.04537849152228725812	;;; 5倍強
10 5.34539230884203582838
11 5.66324587609016097972
$ 

基準の周波数を5にしてみると

$ for i in $(seq 0 11) ; do echo -n "$i " ; echo "5 * e( $i/12 * l(2) )" | bc -l ; done
0 5.00000000000000000000	;;; 5倍
1 5.29731547179647632275
2 5.61231024154686490710
3 5.94603557501360533355	;;; 6倍弱
4 6.29960524947436582375
5 6.67419927085017182405
6 7.07106781186547524390	;;; 7倍強
7 7.49153538438340749390
8 7.93700525984099737360	;;; 8倍弱
9 8.40896415253714543020
10 8.90898718140339304730	;;; 9倍弱
11 9.43874312681693496620
$ 

和音を構成する周波数が、整数倍の関係になってると、美しく響くわけですが、 ちょっと高かったり、ちょっと低かったりで、なんとも悔しい。

で、この悔しいあたりを、整数倍きっちりに補正した音階が、純正律という事ですね。(たぶん)

というか、声でハモったりするのは自然に純正律でやってて、 何かを諦めて妥協した産物が平均律ですね。(たぶん)

何で妥協せねばならなかったか?
よく聞くのは「転調」したとき、純正律は困るという事です。

まぁ何となく、想像はつきます。
先の周波数の比率で、鍵盤の白鍵の部分で、整数倍になるように調整しておいても、 転調して黒鍵も使うようになると、調整したのがあだとなって、 かえって響きが汚くなる事もありそうです。

この「転調」の事と関連してるのかよく理解してませんが...
例えば白鍵の範囲だけで整数倍に調整しても、次の例のような矛盾が出て来ます。

F1	16
A1	20
C2	24
E2	30
G2	36
B2	45
D3	54
F3	64 (F1*4)
A3	80 (A1*4)

の比率に周波数を調整したとして

コードF  , F1:A1:C2 = 16:20:24 = 4:5:6
コードC  , C2:E2:G2 = 24:30:36 = 4:5:6
コードG  , G2:B2:D3 = 36:45:54 = 4:5:6
コードAm , A1:C2:E2 = 20:24:30 = 10:12:15
コードEm , E2:G2:B2 = 30:36:45 = 10:12:15
コードDm , D3:F3:A3 = 54:64:80 = 10.125 : 12 : 15

Dm の組み合わせでは、Dの音の比率が残念な事になります。

なので、整数倍への調整は固定する事はできず、 和音の組み合わせによって、動的に調整せねばなりません。

アコースティックな鍵盤楽器で、転調のたびに調律する訳にもいかず、 妥協して平均律で調律するという歴史ですね。

さて、こちらは鍵盤ON/OFF情報のMIDIデータを入力として、波形を生成するプログラムです。
動的にピッチを調整するというようなややこしい事こそ、計算機にやらせればいいのでは?
などというモチベーションで、純正律にチャレンジしてみましょう。


さてどうしたものか

「ピッチの調整」で思い浮かぶのがMIDIのピッチベンド・イベントです。
ですが、この処理はMIDIチャンネル全体に影響します。
1つのチャンネルで和音を弾いてると、和音全体のピッチがスライドします。
和音を構成する1つ1つの音を調整したいので、この機能ではダメです。

各チャンネルに同じ音色を設定して、チャンネル1つあたり同時に1音だけ割り振って、 昔のモノフォニック・シンセサイザーの多重録音みたいな事をすれば、 出来なくもないですが、MIDIデータの上流側の細工が大変そうなので却下します。

波形を生成するプログラムの中で、ノート番号から周波数に変換してる箇所で、 細工をしかけて調整するようにしてみます。

そして、肝心でもあり難しそうなのは、現在鳴ってる和音の構成から、 基準となるベースの音(スケールのキーとなる音)を見つける処理です。
動的な調整の「肝」の部分です。


和音のキーを探る

まず、今どの音が鳴ってるかは、note.c の note_buf[] に情報があります。
鍵盤ONのイベントがくると、情報がこの note_buf[] に登録されて、 鍵盤OFFのイベントがきても、すぐには削除されません。
エンベロープのリリースタイムを待ち、さらにエフェクターのディレイを考慮して、 音量が 0 まで下がったのを見計らって、こっそり削除してたはずです。

和音が変わる境目で前の和音の残響音が残ってる分は、無視した方が無難に思えるので、 note_buf[] にある鍵盤がONの状態のものだけを対象に、キーの音を探ることにします。

そしてnote_buf[] 上には、ドラムの音も含まれてます。
なのでプログラム番号がドラムの指定になってたり、 構成される音色のtone情報の、ノート番号の指定が全て「固定」されているものは、 判定から除外する事にします。

色々とキーを探る処理をするとしても、 結果のキーの音は、結局1オクターブ中の12個の音のどれかになります。
なので、処理の基本は12回ループの総当たり方式。 キーの音が12個のうちの1つであると仮定して、今鳴ってる複数の音の情報に照らしてみて、 なんらかの評価関数を実行します。
そうして、12個の候補の中で最も得点の高かった人を選ぶ事にします。

では、どんな評価関数にすべしか?
先の整数比の例を元に、こんなテーブルを作ってみました。

static struct{
	int h, l;
} pure_tbl[] = {
	{    1,    1 },	/* C  2 */
	{ 1600, 1500 },
	{    9,    8 },	/* D 17 */
	{  600,  500 },
	{    5,    4 },	/* E  9 */
	{  400,  300 }, /* F */
	{  700,  500 },
	{    3,    2 },	/* G  5 */
	{  800,  500 },
	{  500,  300 }, /* A */
	{  900,  500 },
	{   15,    8 },	/* B 23 */
};

仮定した1つの候補のキーの音に対して、そこからの移動分が配列のインデックスになります。
例えば、候補のキーから1音(半音 x 2)分上の音で考えると、表の中は h=9, l=8
その音と、キーの音との周波数の比率を「9:8」に調整したい事を表します。

仮定したキーの音を C と見立てたときに、白鍵の位置にあって、かつ、お互いにあまり邪魔しない関係のものは、 2桁までの整数にしてます。

逆に、黒鍵や邪魔するところは、3桁以上の大きな数値で比率を設定してます。

この比率のパラメータ「h:l」から (h + l) の値を、評価値として使います。
値が小さいほど、よりふさわしい値となるので、ダメダメポイントと考えてもいいでしょう。

12個のキーから1つを仮定して、鳴ってる音に対して、 このテーブルからダメダメポイントを集計します。 12個のキーの候補の中で、集計したダメダメポイントが最も低かったキーを、選ぶ処理となります。

原理はこんな感じですが、細かく見ていくと問題があります。
note_buf[] の1つの音について、鍵盤ONにしたときの鍵盤の位置と、今なってる音が違うケースがあります。
まさに以前 ああ、ややこしや でやったピッチ・ベンド。

低い音からチョーキングして、本来の音の高さまで引き上げるパターンや、 本来の音の高さから下にズラすパターン。
はたまた、1オクターブ分変化させ中に、たまたま通りすぎてるだけの時。

波形を生成する処理からすると、過去のイベントや未来のイベントを認識してなくて、 その切り取った一瞬だけが与えられています。
現在の状態だけで、何とかそれらしく判定させてみましょう。

ピッチ・ベンドしてる音については、 次のルールで判定の材料にする事にしてみます。

これで、ようやくキーを探り出したら、あとは周波数を調整する処理です。


周波数の調整

波形生成処理で、ノート番号から周波数に変換してVCOへと渡す箇所です。

note.c note_out()関数

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)));
  :

こうして求まったfreqに対して、次の処理を追加します。


その他

あと、キーを探って更新するタイミングですが、 鍵盤のON/OFFがあったときと、ピッチ・ベンドのイベントがあったときに更新するようにします。

prog58 から prog59 の差分です prog59.patch


動かしてみます

まずは、簡単なMIDIデータでお試しから
 ボコーダー (その6) 
で作った b2.txt を b3.txt としてコピーして 、適当に編集します。

b3.txt

MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=0
delta=0 prog_num ch=0 v=50
delta=0 ctl_chg ch_vol_msb ch=0 v=40
delta=0 ctl_chg pan_msb ch=0 v=28
delta=0 on ch=0 note=21 velo=100
delta=0 on ch=0 note=33 velo=100
delta=0 on ch=0 note=40 velo=100
delta=0 on ch=0 note=45 velo=100
delta=192 prog_num ch=1 v=81
delta=0 ctl_chg ch_vol_msb ch=1 v=40
delta=0 ctl_chg pan_msb ch=1 v=100
delta=0 on ch=1 note=49 velo=100
delta=0 on ch=1 note=52 velo=100
delta=0 on ch=1 note=56 velo=100
delta=0 on ch=1 note=59 velo=100
delta=192 prog_num ch=2 v=24
delta=0 ctl_chg ch_vol_msb ch=2 v=127
delta=0 on ch=2 note=69 velo=100
delta=0 on ch=2 note=73 velo=100
delta=0 on ch=2 note=76 velo=100
delta=0 on ch=2 note=80 velo=100
delta=0 on ch=2 note=81 velo=100
delta=0 on ch=2 note=83 velo=100
delta=960 cmd=f8
delta=0 off ch=0 note=21 velo=100
delta=0 off ch=0 note=33 velo=100
delta=0 off ch=0 note=40 velo=100
delta=0 off ch=0 note=45 velo=100
delta=0 off ch=1 note=49 velo=100
delta=0 off ch=1 note=52 velo=100
delta=0 off ch=1 note=56 velo=100
delta=0 off ch=1 note=59 velo=100
delta=0 off ch=2 note=69 velo=100
delta=0 off ch=2 note=73 velo=100
delta=0 off ch=2 note=76 velo=100
delta=0 off ch=2 note=80 velo=100
delta=0 off ch=2 note=81 velo=100
delta=0 off ch=2 note=83 velo=100
delta=192 cmd=f8


平均律で生成して再生
$ cat b3.txt | midi_prog/midtxt -r | midi_prog/prog59 -nocui -vol all:4 -sox b3.mean.wav
$ play b3.mean.wav
$ lame b3.mean.wav b3.mean.mp3

 b3.mean.mp3 


純正律で生成して再生
$ cat b3.txt | midi_prog/midtxt -r | midi_prog/prog59 -nocui -vol all:4 -pure -sox b3.pure.wav
$ play b3.pure.wav
$ lame b3.pure.wav b3.pure.mp3

 b3.pure.mp3 

違いがわかりませんが...!?


$ cat b3.txt | midi_prog/midtxt -r | midi_prog/prog59 -nocui -vol all:4 > b3.mean.raw
$ cat b3.txt | midi_prog/midtxt -r | midi_prog/prog59 -nocui -vol all:4 -pure > b3.pure.raw
$ cmp b3.mean.raw b3.pure.raw
b3.mean.raw b3.pure.raw differ: char 397, line 13
$ 

データ的には違いが出てます (^_^;


もっと判りやすそうなMIDIデータを

b4.txt

MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=0
delta=0 prog_num ch=0 v=49
delta=0 ctl_chg ch_vol_msb ch=0 v=80
delta=200 on ch=0 note=57 velo=100
delta=200 on ch=0 note=61 velo=100
delta=200 on ch=0 note=64 velo=100
delta=200 on ch=0 note=68 velo=100
delta=200 on ch=0 note=69 velo=100
delta=200 on ch=0 note=71 velo=100
delta=500 cmd=f8
delta=200 off ch=0 note=57 velo=100
delta=200 off ch=0 note=61 velo=100
delta=200 off ch=0 note=64 velo=100
delta=200 off ch=0 note=68 velo=100
delta=200 off ch=0 note=69 velo=100
delta=200 off ch=0 note=71 velo=100
delta=200 cmd=f8


平均律で生成して再生
$ cat b4.txt | midi_prog/midtxt -r | midi_prog/prog59 -nocui -vol all:4 -sox b4.mean.wav
$ play b4.mean.wav
$ lame b4.mean.wav b4.mean.mp3

 b4.mean.mp3 


純正律で生成して再生
$ cat b4.txt | midi_prog/midtxt -r | midi_prog/prog59 -nocui -vol all:4 -pure -sox b4.pure.wav
$ play b4.pure.wav
$ lame b4.pure.wav b4.pure.mp3

 b4.pure.mp3 

どうでしょうか?
末尾辺りで鍵盤OFFが入っていくところで、
キーの判定が更新されて、補正が変化してるような気がしますが、
思い込による気のせいかも知れません...。

やっぱり、素人ではよく判らないものなのかも?


これまでのターゲットのMIDIファイルを、ベース(ch=1)の音量大きめで再生して比べてみます。

平均律で生成して再生
$ cat L3007_06.MID | midi_prog/prog59 -nocui -vol all:3,1:6 -sox mean.wav
$ lame mean.wav mean.mp3

 mean.mp3  (サイトのファイル1つあたりのサイズ制限にひっかかってしまい、途中で切れてます)


純正律で生成して再生
$ cat L3007_06.MID | midi_prog/prog59 -nocui -pure -vol all:3,1:6 -sox pure.wav
$ lame pure.wav pure.mp3

 pure.mp3  (サイトのファイル1つあたりのサイズ制限にひっかかってしまい、途中で切れてます)


判りやすい違いとしては、エンディングでピッチ・ベンドで音が高くなっていくあたり、
半音の1/4のゾーンに出たり入ったりで、ギクシャク感がします。

でもそれって純正律の調整の動作確認とは違うような気もするし...、
それ以前に、サイズ制限でファイルが切れててエンディングまで再生できません orz


他の曲でも同様に試してみます。

$ cat L3007_02.MID | midi_prog/prog59 -nocui -vol all:3,1:6 -sox mean_mask.wav
$ lame mean_mask.wav mean_mask.mp3

 mean_mask.mp3 


純正律で生成して再生
$ cat L3007_02.MID | midi_prog/prog59 -nocui -pure -vol all:3,1:6 -sox pure_mask.wav
$ lame pure_mask.wav pure_mask.mp3

 pure_mask.mp3 

なんとなく、純正律の方が澄んだ響き? もうちょっとハッキリした違いを出して、動作確認できないものか...

ドラム以外の音を、シンプルなSIN波だけの音色にして試してみます。
プログラム番号24の設定が

tone.c
  :
	},{
		"24", "-1", /* tango accordion */
		(struct tone_compo_rec []){
			{ "SIN", -1, 1.0 }, { NULL, }
		}
	},{
  :

などとなっていて、名前とはうらはらに、SIN波にしてました。
MIDIファイルの音色の設定部分(プログラム番号を指定するイベント)で、 24を指定するように細工してみます。

まずMIDIファイルをテキストで表示して、プログラム番号指定箇所を確認

$ cat L3007_02.MID | midi_prog/midtxt | grep prog_num
delta=1 prog_num ch=0 v=95
delta=1 prog_num ch=1 v=38
delta=1 prog_num ch=2 v=62
delta=1 prog_num ch=3 v=50
delta=1 prog_num ch=4 v=103
delta=1 prog_num ch=5 v=28
delta=1 prog_num ch=6 v=118
delta=1 prog_num ch=7 v=108
delta=1 prog_num ch=8 v=81
delta=1 prog_num ch=9 v=0
delta=1 prog_num ch=10 v=25
$ 

ドラムの ch=9 は ドラム用の 0 を指定
他は、色々指定してます


これらの行の ch=9 以外のものを v=24 に sed で置換してみます。

sed -e 's/prog_num ch=\([^9]*\) v=[0-9]*/prog_num ch=\1 v=24/'

の指定でいけるはず...
確認してみます

$ cat L3007_02.MID | midi_prog/midtxt | sed -e 's/prog_num ch=\([^9]*\) v=[0-9]*/prog_num ch=\1 v=24/' \
| grep prog_num
delta=1 prog_num ch=0 v=24
delta=1 prog_num ch=1 v=24
delta=1 prog_num ch=2 v=24
delta=1 prog_num ch=3 v=24
delta=1 prog_num ch=4 v=24
delta=1 prog_num ch=5 v=24
delta=1 prog_num ch=6 v=24
delta=1 prog_num ch=7 v=24
delta=1 prog_num ch=8 v=24
delta=1 prog_num ch=9 v=0
delta=1 prog_num ch=10 v=24
$ 

ドラムの ch=9 だけは 0 のままです
OK

では、置換したテキストデータを、MIDIファイルに逆変換して
ファイル sin.mid として保存しておきます

$ cat L3007_02.MID | midi_prog/midtxt | sed -e 's/prog_num ch=\([^9]*\) v=[0-9]*/prog_num ch=\1 v=24/' \
| midi_prog/midtxt -r > sin.mid

では、とりあえず波形をファイルに落として再生

$ cat sin.mid | midi_prog/prog59 -nocui -vol all:5 -sox sin.wav
$ play sin.wav

うーむ
ちょっとエンベロープのサスティンが低くて、比較しにくいかも


プログラム番号24の音色の方も変更してみます


まず、音色の情報をテキストにセーブ

$ echo save | midi_prog/prog59 > tone.txt


tone.txt を tone_sin.txt としてコピー

$ cp tone.txt tone_sin.txt


tone_sin.txt を編集します

次の"SIN"の箇所を

  name "SIN"
  vco
    wave1 0
    wave2 0
    tune 0
    mix 0.000000
    ring 0
    alias_noise1 0
    alias_noise2 0
  fl1
    type 1
    freq 20000.000000
    Q 0.800000
  fl2
    type 0
    freq 0.000000
    Q 0.000000
  env
    attack 0.000000
    decay 0.300000
    sustain 0.200000
    release 0.300000
  level 1.400000
  lfo_modu
    pitch1 25
    pitch2 0
    filter1 0
    filter2 0
  lfo
    wave 0
    freq 6.000000
    delay 0.300000
  env_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  delay
    onoff 0
    sec 0.000000
    gain 0.000000
  chorus 0


次のように変更します

  name "SIN"
  vco
    wave1 0
    wave2 0
    tune 0
    mix 0.000000
    ring 0
    alias_noise1 0
    alias_noise2 0
  fl1
    type 0
    freq 0.000000
    Q 0.000000
  fl2
    type 0
    freq 0.000000
    Q 0.000000
  env
    attack 0.010000
    decay 0.000000
    sustain 1.000000
    release 0.010000
  level 5.000000
  lfo_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  lfo
    wave 0
    freq 6.000000
    delay 0.300000
  env_modu
    pitch1 0
    pitch2 0
    filter1 0
    filter2 0
  delay
    onoff 0
    sec 0.000000
    gain 0.000000
  chorus 0


パッチでみるとこなんな感じです

$ diff -u tone.txt tone_sin.txt
--- tone.txt    2015-06-05 11:05:39.000000000 +0900
+++ tone_sin.txt        2015-06-05 11:19:38.000000000 +0900
@@ -139,21 +139,21 @@
     alias_noise1 0
     alias_noise2 0
   fl1
-    type 1
-    freq 20000.000000
-    Q 0.800000
+    type 0
+    freq 0.000000
+    Q 0.000000
   fl2
     type 0
     freq 0.000000
     Q 0.000000
   env
-    attack 0.000000
-    decay 0.300000
-    sustain 0.200000
-    release 0.300000
-  level 1.400000
+    attack 0.010000
+    decay 0.000000
+    sustain 1.000000
+    release 0.010000
+  level 5.000000
   lfo_modu
-    pitch1 25
+    pitch1 0
     pitch2 0
     filter1 0
     filter2 0
$ 


では、このファイルをロードして試してみます

$ (echo load ; cat tone_sin.txt ; cat sin.mid ) | midi_prog/prog59 -nocui -vol 7:0.3 -sox mean_mask_sin.wav
$ lame mean_mask_sin.wav mean_mask_sin.mp3

 mean_mask_sin.mp3 

(ch=7 の音が多少うるさめなので、-vol で調整してます)


そして -pure 指定追加

$ (echo load ; cat tone_sin.txt ; cat sin.mid ) | midi_prog/prog59 -nocui -vol 7:0.3 -pure -sox pure_mask_sin.wav
$ lame pure_mask_sin.wav pure_mask_sin.mp3

 pure_mask_sin.mp3 

まだよく判らんです

逆に倍音の多いノコギリ波にしてみます

$ cp tone_sin.txt tone_saw.txt

tone_saw.txt 編集して

$ diff -u tone_sin.txt tone_saw.txt
--- tone_sin.txt 2015-06-05 11:19:38.000000000 +0900
+++ tone_saw.txt 2015-06-05 11:29:55.000000000 +0900
@@ -131,7 +131,7 @@
 ,
   name "SIN"
   vco
-    wave1 0
+    wave1 1
     wave2 0
     tune 0
     mix 0.000000

な状態にして


$ (echo load ; cat tone_saw.txt ; cat sin.mid ) | midi_prog/prog59 -nocui -vol all:0.5,9:1,7:0.3 -sox mean_mask_saw.wav
$ lame mean_mask_saw.wav mean_mask_saw.mp3

 mean_mask_saw.mp3 


そして -pure 指定追加

$ (echo load ; cat tone_saw.txt ; cat sin.mid ) | midi_prog/prog59 -nocui -vol all:0.5,9:1,7:0.3 -pure -sox pure_mask_saw.wav
$ lame pure_mask_saw.wav pure_mask_saw.mp3

 pure_mask_saw.mp3 

おお?
pureの方が、かなり力強い感じで響いてる?
ようやく少し違いが判りました

より年波のせいで、高い音に鈍感になってるのかも (T_T)


MIDIファイルのテキスト変換 (その2)

ここらでTea break

以前につくったMIDIファイルのテキスト変換ツールに機能を追加してみます

テキストに変換したたとき、MIDIイベントの先頭には "delta=xxx" として、 イベント時刻がデルタタイムの値で書き出されます。
( デルタタイムとは )

デルタタイムは、いわば前のイベントからの相対時間なので、 これを先頭からの絶対時刻に変換する機能を追加してみます。

オプション -tx_abs を追加します。
この場合、midtxtにはテキスト形式を入力して、 "delta=xxx" で始まる行だけ "abs=xxx" に変換して出力します。
なので、テキストからテキストへの変換です。

後で時刻の順でソート出来るように、数値の表示は左に0をつけて8桁で揃えておきます。

オプション -tx_delta を追加して、逆方向も用意しておきます。
この場合も、テキストからテキストへの変換です。

midtxt2.patch

まずMIDIファイルをテキストに落として

$ cat L3007_02.MID | midi_prog/midtxt > mask.mid.txt


先頭の30行分の中身を確認

$ head -n 30 mask.mid.txt
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=62660
delta=0 meta type=3 n=15 42 65 68 69 6e 64 20 74 68 65 20 4d 61 73 6b
delta=0 meta type=2 n=26 28 43 29 31 39 39 33 20 52 6f 6c 61 6e 64 20 43 6f 72 70 6f 72 61 74 69 6f 6e
delta=0 meta type=88 n=4 04 02 18 08
delta=0 meta set_tempo n=3 tempo=454545
delta=0 cmd=f0 type=10 41 10 42 12 40 00 7f 00 41 f7
delta=48 cmd=f0 type=25 41 10 42 12 40 01 10 02 04 02 02 06 02 02 01 01 01 01 00 00 00 00 00 17 f7
delta=2 cmd=f0 type=10 41 10 42 12 40 1a 15 02 0f f7
delta=46 ctl_chg type=0 ch=0 v=0
delta=1 ctl_chg type=32 ch=0 v=0
delta=1 prog_num ch=0 v=95
delta=1 ctl_chg ch_vol_msb ch=0 v=100
delta=0 ctl_chg pan_msb ch=0 v=64
delta=0 ctl_chg type=11 ch=0 v=60
delta=1 ctl_chg type=91 ch=0 v=115
delta=0 ctl_chg type=93 ch=0 v=60
delta=1 ctl_chg type=0 ch=1 v=0
delta=1 ctl_chg type=32 ch=1 v=0
delta=1 prog_num ch=1 v=38
delta=1 ctl_chg ch_vol_msb ch=1 v=100
delta=0 ctl_chg pan_msb ch=1 v=64
delta=0 ctl_chg type=11 ch=1 v=127
delta=1 ctl_chg type=91 ch=1 v=30
delta=0 ctl_chg type=93 ch=1 v=10
$ 


イベント時間を絶対時間に変換して、先頭の30行分を確認

$ cat mask.mid.txt | midi_prog/midtxt -tx_abs | head -n 30
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=62660
abs=00000000 meta type=3 n=15 42 65 68 69 6e 64 20 74 68 65 20 4d 61 73 6b
abs=00000000 meta type=2 n=26 28 43 29 31 39 39 33 20 52 6f 6c 61 6e 64 20 43 6f 72 70 6f 72 61 74 69 6f 6e
abs=00000000 meta type=88 n=4 04 02 18 08
abs=00000000 meta set_tempo n=3 tempo=454545
abs=00000000 cmd=f0 type=10 41 10 42 12 40 00 7f 00 41 f7
abs=00000048 cmd=f0 type=25 41 10 42 12 40 01 10 02 04 02 02 06 02 02 01 01 01 01 00 00 00 00 00 17 f7
abs=00000050 cmd=f0 type=10 41 10 42 12 40 1a 15 02 0f f7
abs=00000096 ctl_chg type=0 ch=0 v=0
abs=00000097 ctl_chg type=32 ch=0 v=0
abs=00000098 prog_num ch=0 v=95
abs=00000099 ctl_chg ch_vol_msb ch=0 v=100
abs=00000099 ctl_chg pan_msb ch=0 v=64
abs=00000099 ctl_chg type=11 ch=0 v=60
abs=00000100 ctl_chg type=91 ch=0 v=115
abs=00000100 ctl_chg type=93 ch=0 v=60
abs=00000101 ctl_chg type=0 ch=1 v=0
abs=00000102 ctl_chg type=32 ch=1 v=0
abs=00000103 prog_num ch=1 v=38
abs=00000104 ctl_chg ch_vol_msb ch=1 v=100
abs=00000104 ctl_chg pan_msb ch=1 v=64
abs=00000104 ctl_chg type=11 ch=1 v=127
abs=00000105 ctl_chg type=91 ch=1 v=30
abs=00000105 ctl_chg type=93 ch=1 v=10
$ 

問題なさそう

さらに、絶対時間から、デルタタイムに逆変換かけて
元のファイルと比較

$ cat mask.mid.txt | midi_prog/midtxt -tx_abs | midi_prog/midtxt -tx_delta | diff - mask.mid.txt
$ 

一致
OK


MIDIファイルのテキスト変換 (その3)

さて、ここらで「打込み」を試してみたくなってきました。

「打込み」でMIDIファイルを作り易いように、テキスト変換ツールmidtxt に機能を追加しておきます。

まず、音符を打込むにあたり、ノートONとノートOFFの2つのイベントを 指定するのが面倒です。
音符の長さを指定すると、頭でノートON、お尻でノートOFFに変換するようにしてみます。

音符の長さも、デルタタイムの指定が直感的じゃなく、扱いづらいです。
4分音符は 1/4, 8分音符は 1/8, 全音符は 1 と、小節の単位で分数でも扱えるようにします。

ノート番号の数値も人間に厳しいものです。
A#4 や C4 などや、ドラムではHC (Hi-hat Close) や SD (Snare Drum) で記述出来るようにします。

midtxtコマンドに -len オプションを追加します。
行の先頭が"len="で始まるイベント行は、上記のフォーマットで音符のイベントが記述されているとみなして、 通常の"delta="で始まるノートON, ノートOFFイベントに変換します。

なので、この -len オプションもテキストからテキストへの変換になります。

行の先頭が"len="以外の場合は、基本的にそのまま出力しますが、コメントを入れれるようにしておきます。 行の先頭が'#'や';'で始まってる場合や、空行の場合は、無視して何も出力しないようにします。

ついでに、キー・トランスポーズ機能も追加します。

次の指定で、ノートON、ノートOFFのイベント行のノート番号に、指定した数値を加算します。

-tx_trans 数値 

この -tx_trans オプションは -len とは別で、 テキストデータの、通常のノートON、ノートOFFイベントについて変換します。

ドラムパートのチャンネルまで変換してしまうと、 打楽器の音が変ってしまいますので、 指定のチャンネルだけ変換するようにします。

デフォルトは、お約束の9チャンネルだけは変換しない設定とします。
デフォルトから変更したいときは、調整用のボリュームのときの指定と同様に、次の形式で指定します。

-tx_trans_enable 8:0
    デフォルトの状態から、さらに8チャンネルも変換しない設定

-tx_trans_enable 8:0,10:0
    デフォルトの状態から、さらに8チャンネルと10チャンネルも変換しない設定

-tx_trans_enable 8:0,10:0,9:1
    デフォルトの状態から、さらに8チャンネルと10チャンネルも変換しない設定にし、9チャンネルは変換する設定

-tx_trans_enable all:1
    全てのチャンネルで変換する設定

-tx_trans_enable all:0
    全てのチャンネルで変換しない設定

-tx_trans_enable all:0,15:1
    15チャンネルだけ変換する設定

(ただし、-tx_trans_enableオプションは、-tx_transオプションが指定されてる時のみ有効)

ch.cやvoco.cにある、この形式のオプションの処理を、 opt_ch_loop_get()関数, opt_ch_loop_get_dbl()関数 としてutil.[ch]に切り出して、 それを使用するように変更しておきます。

midtxt3.patch


「打ち込み」してみる

テキスト変換ツール midtxt を駆使して、どのくらい「打ち込み」できるものか試してみます。

ここは古典的に YMO の Rydeen をひとつ。
お試しなので、冒頭のほんの「さわり」だけにします。
midtxtコマンドの呼び出しが多くなるので、スクリプトファイルに書くことにします。


「チッチキチッチキ」のお試し

お馴染みオープニングのハイハットの「チッチキチッチキ」から試してみます。

まずは、スクリプトファイル ryd.sh を用意

$ touch ryd.sh
$ chmod a+x ryd.sh

ryd.sh を編集して次の内容にします

$ cat ryd.sh
#!/bin/sh

mkdir -p tmp
cd tmp

MIDTXT=../midi_prog/midtxt
PROG=../midi_prog/prog59

#
# Header
#
cat ../L3007_02.MID | $MIDTXT | head -n 7 > hdr.txt

#
# Setting
#
cat > setting.txt <<EOF
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
EOF

#
# Hihat-Close
#
$MIDTXT -len > hc.txt <<EOF
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=R
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=HC
EOF

cat hdr.txt setting.txt hc.txt hc.txt hc.txt hc.txt > all.txt

cat all.txt | $MIDTXT -r > ryd.mid
cat ryd.mid | $PROG -nocui -sox ryd.wav
lame ryd.wav ../ryd-1.mp3

# EOF
$ 


スクリプトなので '#' で始まる行はコメントです

冒頭で作業用のディレクトリ tmp を作り、そこに移動して作業します

繁雑さを避けるため、midtxtコマンドとprog59コマンドを、
変数MIDTXTと変数PROGに設定

MIDIデータの冒頭のヘッダ部分はお約束の内容なので、
既存のMIDIファイルの内容の先頭の7行分を拝借

cat ../L3007_02.MID | $MIDTXT | head -n 7 > hdr.txt

まぁ自分で入力したとしても

$ cat tmp/hdr.txt 
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=62660
$ 

だけの内容です

末尾のtrksz指定は、prog59 では読むだけ読んで無視してるので、
とりあえずこのままで ;-p)


次に、チャンネル9にドラム用のプログラム番号0を設定し、
チャンネル9の音量を、とりあえず100(最大127)に設定

cat > setting.txt <<EOF
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
EOF


続いて、ハイハットの「チッチキ」の打ち込みです

$MIDTXT -len > hc.txt <<EOF
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=R
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=HC
EOF


16分音符のデータが4つ
ノート番号HCは HiHat-Close を、R は休符を表します。

midtxt.c
  :
int
str_to_note(char *note_buf)
{
	struct{
		char *s;
		int note;
	}fix_tbl[] = {
		{ "R", -1 },
		{ "BD_Ac", 35 },
		{ "BD1", 36 },
		{ "SideStick", 37 },
		{ "SD_Ac", 38 },
		{ "HandClup", 39 },
		{ "SD_El", 40 },
		{ "LowFloorTom", 41 },
		{ "HC", 42 },
		{ "HighFloorTom", 43 },
  :

なので、R はノート番号 -1, HC はノート番号 42 に変換されます。
ただし、-1 は内部で特別な値として使用してて、
何もしないMIDIクロック・イベントに置き換えられます
(MIDIクロック・イベントは  まずはアイデアをお試し  で使ってました)


変換後の hc.txt を見てみると

$ cat tmp/hc.txt
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
delta=0 cmd=f8
delta=24 cmd=f8
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
$ 

これが、四分音符1個分の「チッチキ」に相当します


そして、これらヘッダ部分と設定部分に続いて、
「チッチキ」4回分を、all.txt にまとめて

cat hdr.txt setting.txt hc.txt hc.txt hc.txt hc.txt > all.txt


テキストからMIDIデータに変換

cat all.txt | $MIDTXT -r > ryd.mid


MIDIデータを prog59 に入れて波形を生成して ryd.wav に落とします

cat ryd.mid | $PROG -nocui -sox ryd.wav


それでは、実行してファイルを生成

$ ./ryd.sh

そして再生

$ play tmp/ryd.wav

ちょっと音量が小さいですが「チッチキ」鳴ってます

チャンネルのボリュームは127中の100に設定してるので、
prog59の調整用の方のボリュームで音を大きくしてみます

ryd.sh の
cat ryd.mid | $PROG -nocui -sox ryd.wav
行を
cat ryd.mid | $PROG -nocui -vol all:5 -sox ryd.wav
に変更して、音量を5倍に

$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-1.mp3 

まずは、「チッチキ」のお試しOKです。


続いてドラム

バスドラムとスネアドラムで「ドンパンドンパン」を入れてみます。

メロディーラインの入る所を起点に考えると弱起の曲なので、 2拍分喰って始まる感じです。

2泊分休みがあって、バスドラとスネアを同時に2拍打って、あとは「ドンパン」の連続が3小節分+2拍分まで。
また、頭にもどってバスドラとスネアを同時に2拍打って、あとは「ドンパン」の連続3小節分+2拍分まで。
最後に、2拍のフィルで「ドンパパパパパン」

1(弱起の分) + 4 + 4 小節で、合計 9 小節分作ってみます。

2拍分を単位にまとめて作ってみます。

ryd.sh
  :
#
# Hihat-Close
#
$MIDTXT -len > hc.txt <<EOF
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=R
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=HC
EOF

#
# Drums
#
$MIDTXT -len > dr_r.txt <<EOF
len=1/2 ch=9 note=R
EOF

$MIDTXT -len > dr_1.txt <<EOF
len=1/4 ch=9 note=BD1,SD_El
len=1/4 ch=9 note=BD1,SD_El
EOF

$MIDTXT -len > dr_2.txt <<EOF
len=1/4 ch=9 note=BD1
len=1/4 ch=9 note=SD_El
EOF

$MIDTXT -len > dr_3.txt <<EOF
len=1/8  ch=9 note=BD1
len=1/16 ch=9 note=SD_El
len=1/16 ch=9 note=SD_El
len=1/16 ch=9 note=SD_El
len=1/16 ch=9 note=SD_El
len=1/8  ch=9 note=SD_El
EOF

cat dr_r.txt dr_1.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_2.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_1.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_2.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_3.txt \
 dr_r.txt \
 > dr_all.txt

  :

dr_r.txt は休符
dr_1.txt はバスドラ、スネアの同時打ち
dr_2.txt は「ドンパン」
dr_3.txt は最後のフィル部分

いづれも2拍分のデータで、
それらを連結して dr_all.txt にまとめます
最後が「ブチ」っと終るのもなんなので2拍分の休符を追加


とりあえず

cat hdr.txt setting.txt hc.txt hc.txt hc.txt hc.txt > all.txt

の行を

#cat hdr.txt setting.txt hc.txt hc.txt hc.txt hc.txt > all.txt
cat hdr.txt setting.txt hc.txt hc.txt hc.txt hc.txt dr_all.txt > all.txt

に変更して確認してみます

$ ./ryd.sh
$ play tmp/ryd.wav

「チッチキ...ダンダン、ドンパン...」

個別にそれらしく鳴ってます。

では、「ハイハット」と「バスドラ・スネア」とを同時に鳴るように混ぜてみます

ハイハットは「チッチキ」のパターンを必要な個数分揃えておきます

ryd.sh
  :
#
# Hihat-Close
#
$MIDTXT -len > hc.txt <<EOF
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=R
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=HC
EOF

cat hc.txt hc.txt hc.txt hc.txt > hc_4.txt
cat hc_4.txt \
 hc_4.txt hc_4.txt hc_4.txt hc_4.txt \
 hc_4.txt hc_4.txt hc_4.txt hc_4.txt \
 > hc_all.txt

#
# Drums
#
  :

hc_all.txt と dr_all.txt との合成ですが、ここで絶対時間の変換を使います。

ryd.sh 全体の内容

$ cat ryd.sh
#!/bin/sh

mkdir -p tmp
cd tmp

MIDTXT=../midi_prog/midtxt
PROG=../midi_prog/prog59

#
# Header
#
cat ../L3007_02.MID | $MIDTXT | head -n 7 > hdr.txt

#
# Setting
#
cat > setting.txt <<EOF
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
EOF

#
# Hihat-Close
#
$MIDTXT -len > hc.txt <<EOF
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=R
len=1/16 ch=9 note=HC
len=1/16 ch=9 note=HC
EOF

cat hc.txt hc.txt hc.txt hc.txt > hc_4.txt
cat hc_4.txt \
 hc_4.txt hc_4.txt hc_4.txt hc_4.txt \
 hc_4.txt hc_4.txt hc_4.txt hc_4.txt \
 > hc_all.txt

#
# Drums
#
$MIDTXT -len > dr_r.txt <<EOF
len=1/2 ch=9 note=R
EOF

$MIDTXT -len > dr_1.txt <<EOF
len=1/4 ch=9 note=BD1,SD_El
len=1/4 ch=9 note=BD1,SD_El
EOF

$MIDTXT -len > dr_2.txt <<EOF
len=1/4 ch=9 note=BD1
len=1/4 ch=9 note=SD_El
EOF

$MIDTXT -len > dr_3.txt <<EOF
len=1/8  ch=9 note=BD1
len=1/16 ch=9 note=SD_El
len=1/16 ch=9 note=SD_El
len=1/16 ch=9 note=SD_El
len=1/16 ch=9 note=SD_El
len=1/8  ch=9 note=SD_El
EOF

cat dr_r.txt dr_1.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_2.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_1.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_2.txt \
 dr_2.txt dr_2.txt dr_2.txt dr_3.txt \
 dr_r.txt \
 > dr_all.txt


( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta > note.txt

cat hdr.txt setting.txt note.txt > all.txt

cat all.txt | $MIDTXT -r > ryd.mid
cat ryd.mid | $PROG -nocui -vol all:5 -sox ryd.wav
lame ryd.wav ../ryd-2.mp3

# EOF
$ 

末尾の少し手前

( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta > note.txt

cat hdr.txt setting.txt note.txt > all.txt

ここがポイントです

hc_all.txt , dr_all.txt それぞれを midtxt -tx_abs で絶対時間に変換し、 その結果を連結したものを、sortコマンドで時間順に整列します。

ソートした結果を midtxt -tx_delta で、デルタタイムの相対時間形式に戻します。
こうして混合したデータを note.txt として保存。

混ぜた結果の note.txt の中身を確認

$ head -n 30 tmp/note.txt
delta=0 cmd=f8
delta=0 on ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 off ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
delta=0 on ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 off ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
delta=0 on ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 off ch=9 note=42 velo=100
delta=0 on ch=9 note=36 velo=100
delta=0 on ch=9 note=40 velo=100
delta=0 on ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 off ch=9 note=42 velo=100
delta=24 cmd=f8
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=42 velo=100
delta=0 on ch=9 note=42 velo=100
delta=24 off ch=9 note=36 velo=100
delta=0 off ch=9 note=40 velo=100
delta=0 off ch=9 note=42 velo=100
$ 

note=42がハイハット
note=36がバスドラム
note=40がスネアドラム
無事に、混ざってる様子です

hdr.txt setting.txt note.txt の順に連結して、all.txt としてい保存。
以降は、これまで通りテキストからMIDIデータに変換して、波形を生成します。

$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-2.mp3 

バスドラム、スネアドラム OKです。


続いてリード

主旋律を入れてみます。

ご本尊を聞いてみた感じ、キーはF#みたいですが、 '#'の入力が多くて面倒なので、一旦キーがFの状態で打ち込みます。
後で midtxt -tx_trasn 1 で変換して、キーを半音上げておきます。

ryd.sh の追加・更新箇所

  :
#
# Lead
#
$MIDTXT -len > ld_0.txt <<EOF
len=1/2  ch=2 note=R
EOF

$MIDTXT -len > ld_1.txt <<EOF
len=1/4  ch=2 note=D6
len=1/4  ch=2 note=E6
len=1    ch=2 note=F6
len=1/8  ch=2 note=R
len=1/8  ch=2 note=F6
len=1/8  ch=2 note=G6
len=1/8  ch=2 note=F6
len=1/8  ch=2 note=E6
len=1/16 ch=2 note=E6
len=1/16 ch=2 note=D6
len=1/8  ch=2 note=C6
EOF

$MIDTXT -len > ld_2.txt <<EOF
len=1/8  ch=2 note=A5
len=1    ch=2 note=D6 <
len=1/2  ch=2 note=D6 >
EOF

$MIDTXT -len > ld_3.txt <<EOF
len=1/8  ch=2 note=D6
len=1    ch=2 note=A6 <
len=1/2  ch=2 note=A6 >
EOF

cat ld_0.txt ld_1.txt ld_2.txt ld_1.txt ld_3.txt > ld_all.txt


( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
 (cat ld_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta | $MIDTXT -tx_trans 1 > note.txt

  :

len=で始まる音符の行で、行末に '<' と '>' を使ってる箇所があります。
これは「スラー」対応で、前後の音符を合体させる事を指定してます。

実装としては、開始ではノートONと、クロック・イベントにして、 末尾ではクロック・イベントと、ノートOFFになるようにしてます。

別にスラーで2行にしなくても

len=1    ch=2 note=D6 <
len=1/2  ch=2 note=D6 >
ならば

len=3/2  ch=2 note=D6
でもいいんですけどね

半音上げてる箇所は

( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
 (cat ld_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta | $MIDTXT -tx_trans 1 > note.txt

ここで、デルタタイム形式に戻した結果を、midtxt -tx_trans 1 で ノート番号に +1 してから、ファイル note.txt に落してます。

$ ./ryd.sh
$ play tmp/ryd.wav

うーむ。デフォルト設定では、テンポがもっさりしすぎです


setting.txt にテンポの設定を追加しときます

テンポの設定の形式はどうだったか?
既存のMIIDファイルで確認してみます

$ cat L3007_02.MID | midi_prog/midtxt | grep tempo
delta=0 meta set_tempo n=3 tempo=454545
$ 

メタイベントで一拍の長さをマイクロ秒単位で指定してました
とりあえず 420 msec くらいにしておきます

ryd.sh
  :
#
# Setting
#
cat > setting.txt <<EOF
delta=0 meta set_tempo n=3 tempo=420000
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
delta=0 prog_num ch=2 v=50
delta=0 ctl_chg ch_vol_msb ch=2 v=40
EOF
  :


$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-3.mp3 

リード OKです。


続いてベース

何回も聞いてベースラインを探ってみました。
こんな感じだと思います (^_^;

ryd.sh の追加・更新箇所

  :

MIDTXT=../midi_prog/midtxt
PROG=../midi_prog/prog59
VOL=all:5,1:10

  :

#
# Setting
#
cat > setting.txt <<EOF
delta=0 meta set_tempo n=3 tempo=420000
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
delta=0 prog_num ch=2 v=50
delta=0 ctl_chg ch_vol_msb ch=2 v=40
delta=0 prog_num ch=1 v=35
delta=0 ctl_chg ch_vol_msb ch=1 v=100
EOF

  :

#
# Bass
#
$MIDTXT -len > bs_0.txt <<EOF
len=1/2 ch=1 note=R
EOF

$MIDTXT -len > bs_1.txt <<EOF
len=1/4 ch=1 note=A#2
len=1/4 ch=1 note=A2
len=3/8 ch=1 note=D3
len=3/8 ch=1 note=E3
len=3/8 ch=1 note=F3
len=3/8 ch=1 note=D3
len=1/4 ch=1 note=C3
len=1/4 ch=1 note=F3
len=1/2 ch=1 note=A#2
EOF

$MIDTXT -len > bs_2.txt <<EOF
len=1/8 ch=1 note=R
len=1/4 ch=1 note=A2
len=1/8 ch=1 note=G2 <
len=1/2 ch=1 note=G2 >
EOF

$MIDTXT -len > bs_3.txt <<EOF
len=1/2 ch=1 note=A#2
len=1/2 ch=1 note=A#2
len=1/4 ch=1 note=A#2
len=1/4 ch=1 note=A2
EOF

cat bs_0.txt bs_1.txt bs_2.txt bs_1.txt bs_3.txt > bs_all.txt

#
# Mix
#
( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
 (cat ld_all.txt | $MIDTXT -tx_abs) ; \
 (cat bs_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta | $MIDTXT -tx_trans 1 > note.txt

cat hdr.txt setting.txt note.txt > all.txt

cat all.txt | $MIDTXT -r > ryd.mid
cat ryd.mid | $PROG -nocui -vol $VOL -sox ryd.wav
lame ryd.wav ../ryd-4.mp3

# EOF

調整用のボリュームの値を、冒頭の変数VOLとして設定しました。
波形生成時 prog59 の -vol の値として変数VOLを参照してます。

VOL=all:5,1:10
  :
cat ryd.mid | $PROG -nocui -vol $VOL -sox ryd.wav

setting.txt では、末尾にch=1としてベースの設定を追加

苦労して探ったベースラインの打ち込みが

#
# Bass
#

のコメント以下の箇所です

各パートを混合する Mix の箇所にもベースの行を追加

$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-4.mp3 

ベース OKです。


そしてバッキングのコード

伴奏のコードを足してみます。

素人なのでコードを探るのが難関でした。
正確には合って無いんでしょうが、こんな感じに聞こえてたかと思います。

メロディーラインとベースラインから、 邪魔しないコードをぼちぼち探ってみました。
キーは半音上げる処理の前の、Fとしてます。

最初の2拍は

Bass	Lead
----	----
A#	D
A	E

素直にそのままBassをとると
A#
Am
です

Bass	Lead	すなお
----	----	------
A#	D	A#
A	E	Am


3拍目以降が

Bass	Lead	すなお
----	----	------
D	F	Dm
E		!
F		F
D	F	Dm
	G	!
	F	Dm
C	E	C
	E
	D	C9
F	C	F
	A	F

ベースが必ずしもコードのキーじゃないとしたら
(素人なので用語「キー」の定義が曖昧です)
(ここではコードのベースの音のつもりで使ってます)

Bass	Lead	すなお	ひねり	あわせ技
----	----	------	------	--------
D	F	Dm		Dm7
E		!	FM7	FM7
F		F	F	Dm7
D	F	Dm
	G	!	Gm	(Gm)
	F	Dm		Dm7
C	E	C		(FM7)
	E
	D	C9	Dm7	Dm7
F	C	F		Dm7
	A	F		Dm7

な感じかなと

()は、もう、Dm7 のままひっぱって省略


そのまま次を追っかけると

Bass	Lead	すなお	ひねり	あわせ技
----	----	------	------	--------
A#	D	A#
A		!	Dm	A#M7 (A#がほしい気がするので)
G		Gm		Gm7  (なんとなく..)


ここで最初に戻って

Bass	Lead	すなお	ひねり	あわせ技
----	----	------	------	--------
A#	D	A#
A	E	Am		FM7  (緊張感で、こっちかと)

という事で先頭箇所も FM7 に


そして、2回目の最後の部分は

Bass	Lead	すなお	ひねり	あわせ技
----	----	------	------	--------
A#	A	!	A#M7
A		Am

ここは、あわせ技なしでそのまま A#M7 と Am

などと自己流で探ってみましたが、はずしてるかもしれません。

ryd.sh の追加・更新箇所

  :

#
# Setting
#
cat > setting.txt <<EOF
delta=0 meta set_tempo n=3 tempo=420000
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
delta=0 prog_num ch=2 v=50
delta=0 ctl_chg ch_vol_msb ch=2 v=40
delta=0 prog_num ch=1 v=35
delta=0 ctl_chg ch_vol_msb ch=1 v=100
delta=0 prog_num ch=4 v=24
delta=0 ctl_chg ch_vol_msb ch=4 v=100
EOF

  :

#
# Backing
#
$MIDTXT -len > bk_0.txt <<EOF
len=1/2 ch=4 note=R
EOF

$MIDTXT -len > bk_1.txt <<EOF
# A#
len=1/4 ch=4 note=A#6,D7,F7

# FM7
len=1/4 ch=4 note=A6,C7,E7,F7

# Dm7
len=3/8 ch=4 note=F6,A6,C7,D7

# FM7
len=3/8 ch=4 note=F6,A6,C7,E7

# Dm7
len=5/4 ch=4 note=A6,C7,D7,F7,A7
EOF

$MIDTXT -len > bk_2.txt <<EOF
# A#
len=1/2 ch=4 note=A#6,D7,F7
len=1/8 ch=4 note=R

# A#M7
len=1/4 ch=4 note=A6,A#6,D7,F7

# Gm7
len=5/8 ch=4 note=G6,A#6,D7,F7
EOF

$MIDTXT -len > bk_3.txt <<EOF
# A#M7
len=1/2 ch=4 note=F6,A6,A#6,D7
len=1/2 ch=4 note=A6,A#6,D7,F7
len=1/2 ch=4 note=A#6,D7,F7,A7
len=1/4 ch=4 note=D7,F7,A7,A#7

# Am
len=1/4 ch=4 note=C7,E7,A7
EOF

cat bk_0.txt bk_1.txt bk_2.txt bk_1.txt bk_3.txt \
 | $MIDTXT -tx_trans -12 > bk_all.txt


#
# Mix
#
( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
 (cat ld_all.txt | $MIDTXT -tx_abs) ; \
 (cat bs_all.txt | $MIDTXT -tx_abs) ; \
 (cat bk_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta | $MIDTXT -tx_trans 1 > note.txt

cat hdr.txt setting.txt note.txt > all.txt

cat all.txt | $MIDTXT -r > ryd.mid
cat ryd.mid | $PROG -nocui -vol $VOL -sox ryd.wav
lame ryd.wav ../ryd-5.mp3

# EOF

midtxt -len では、'#'で始まる行をコメントとして無視する機能をつけたので、 探りだしたコードの名前をコメントとして追加しておきました。

がんばって打ち込んでみたものの、試してみたらちょっと高い感じでした。
まとめて bk_all.txt に落すところで

cat bk_0.txt bk_1.txt bk_2.txt bk_1.txt bk_3.txt \
 | $MIDTXT -tx_trans -12 > bk_all.txt

"-tx_trans -12" で1オクターブ下にズラしてます。
(ああ、便利〜)

$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-5.mp3 

バッキングのコード... なんとかOKです。


「ビギビギ」リズム

何というのでしょうか?
「ビギビギ」なってるパートを入れてみます。

いかにも「打ち込み」でないと演奏できない感じのヤツです。

基本的にコードのキー(ベース)の音と、5度が入るくらいに聞こえてます。

ここで、探ったコードと照らすと

Bass	Lead	すなお	ひねり	あわせ技	ビギビギ
----	----	------	------	--------	--------
D	F	Dm		Dm7		D,A
E		!	FM7	FM7		D,A
F		F	F	Dm7
D	F	Dm
	G	!	Gm	(Gm)		D,G (!)
	F	Dm		Dm7		D,A
C	E	C		(FM7)		D,A
	E
	D	C9	Dm7	Dm7		D,A
F	C	F		Dm7
	A	F		Dm7
----	----	------	------	--------
A#	D	A#				A#,F
A		!	Dm	A#M7		A#,F
G		Gm		Gm7		A#,F
----	----	------	------	--------
A#	D	A#				休
A	E	Am		FM7		休
----	----	------	------	--------
A#	A	!	A#M7			A#,F
A		Am				A,E


この関係で2拍を単位として並べると

D ,A / D, A / D, G / D, A
A#,F / A#,F / A#,F / 休
D ,A / D, A / D, G / D, A
A#,F / A#,F / A#,F / (A# / A, E)

最後の2拍については特別で、前半はA#、後半は A, E のパターン

まず D, A のパターンを作っておて、 A#, Fのパターン は -tx_trasn でズラせばいけそうです。

ryd.sh の追加・更新箇所

  :

#
# Setting
#
cat > setting.txt <<EOF
delta=0 meta set_tempo n=3 tempo=420000
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100
delta=0 prog_num ch=2 v=50
delta=0 ctl_chg ch_vol_msb ch=2 v=40
delta=0 prog_num ch=1 v=35
delta=0 ctl_chg ch_vol_msb ch=1 v=100
delta=0 prog_num ch=4 v=24
delta=0 ctl_chg ch_vol_msb ch=4 v=100
delta=0 prog_num ch=3 v=81
delta=0 ctl_chg ch_vol_msb ch=3 v=100
EOF

  :

#
# Rhythm
#
$MIDTXT -len > rt_0.txt <<EOF
len=1/2 ch=3 note=R
EOF

# D, A
$MIDTXT -len > rt_1.txt <<EOF
len=1/16(1/32) ch=3 note=D4
EOF

$MIDTXT -len > rt_2.txt <<EOF
len=1/16(1/32) ch=3 note=D5
len=1/16(1/32) ch=3 note=A4
EOF

cat rt_1.txt rt_1.txt rt_1.txt rt_1.txt \
    rt_1.txt rt_1.txt rt_2.txt \
    > rt_3.txt

# D, G
$MIDTXT -len > rt_4.txt <<EOF
len=1/16(1/32) ch=3 note=D5
len=1/16(1/32) ch=3 note=G4
EOF

cat rt_1.txt rt_1.txt rt_1.txt rt_1.txt \
    rt_1.txt rt_1.txt rt_4.txt \
    > rt_5.txt

# A#, F
cat rt_3.txt | $MIDTXT -tx_trans -4 > rt_6.txt

# A#, A, E
(( \
 cat rt_1.txt rt_1.txt rt_1.txt rt_1.txt \
 | $MIDTXT -tx_trans -4 \
) ; ( \
 cat rt_1.txt rt_1.txt rt_2.txt \
 | $MIDTXT -tx_trans -5 \
)) > rt_7.txt

cat rt_0.txt rt_0.txt \
 rt_3.txt rt_3.txt rt_5.txt rt_3.txt \
 rt_6.txt rt_6.txt rt_6.txt rt_0.txt \
 rt_3.txt rt_3.txt rt_5.txt rt_3.txt \
 rt_6.txt rt_6.txt rt_6.txt rt_7.txt \
 > rt_all.txt


#
# Mix
#
( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
 (cat ld_all.txt | $MIDTXT -tx_abs) ; \
 (cat bs_all.txt | $MIDTXT -tx_abs) ; \
 (cat bk_all.txt | $MIDTXT -tx_abs) ; \
 (cat rt_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta | $MIDTXT -tx_trans 1 > note.txt

cat hdr.txt setting.txt note.txt > all.txt

cat all.txt | $MIDTXT -r > ryd.mid
cat ryd.mid | $PROG -nocui -vol $VOL -sox ryd.wav
lame ryd.wav ../ryd-6.mp3

# EOF

ここで行頭が len=H/L(h/l) で始まる形式を使ってます。
これはスタッカートのような指定で、()の中が実際に鳴る音の長さになります。
余った長さは休符になります。

ただし、()の中のh/lの長さが、H/Lの長さよりも長く指定する事は、想定してないので禁止です。
その際、動作は保証されません ;-p)

別に、音符と休符の2行に分けて書いてもいいのですが、面倒なので用意した糖衣構文です。

$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-6.mp3 

「ビキビキ」リズムの音、OKです。


左右に振ってみる

ここまで全てのチャンネルがデフォルトのPAN設定のままなので、モノラルで鳴ってました。
ちょっと左右に振ってみます。

PAN設定はどうだったか?
例によって、既存のMIDIファイルを見てみます。

$ cat L3007_02.MID | midi_prog/midtxt | grep pan | head
delta=0 ctl_chg pan_msb ch=0 v=64
delta=0 ctl_chg pan_msb ch=1 v=64
delta=0 ctl_chg pan_msb ch=2 v=74
delta=0 ctl_chg pan_msb ch=3 v=64
delta=0 ctl_chg pan_msb ch=4 v=64
delta=0 ctl_chg pan_msb ch=5 v=84
delta=0 ctl_chg pan_msb ch=6 v=64
delta=0 ctl_chg pan_msb ch=7 v=64
delta=0 ctl_chg pan_msb ch=8 v=64
delta=0 ctl_chg pan_msb ch=9 v=64
$ 

0が左、64でセンター、127で右でした。

ryd.sh の追加・更新箇所

  :

#
# Setting
#
sed -e /^#.*/d -e /^$/d > setting.txt <<EOF
delta=0 meta set_tempo n=3 tempo=420000

# Drums
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100

# Lead
delta=0 prog_num ch=2 v=50
delta=0 ctl_chg ch_vol_msb ch=2 v=40
delta=0 ctl_chg pan_msb ch=2 v=54

# Bass
delta=0 prog_num ch=1 v=35
delta=0 ctl_chg ch_vol_msb ch=1 v=100
delta=0 ctl_chg pan_msb ch=1 v=74

# Backing
delta=0 prog_num ch=4 v=24
delta=0 ctl_chg ch_vol_msb ch=4 v=100
delta=0 ctl_chg pan_msb ch=4 v=84

# Rhythm
delta=0 prog_num ch=3 v=81
delta=0 ctl_chg ch_vol_msb ch=3 v=100
delta=0 ctl_chg pan_msb ch=3 v=44
EOF

  :

lame ryd.wav ../ryd-7.mp3

# EOF

setting.txt 生成箇所で、 catからsedに変更し、行頭'#'の行と、空行を削除してコメント対応しておきます。

ドラムは中央で、リードとベースを少し左と右にズラしておいて、 バッキングとリズムパターンをさらに右と左にズラしてみます。

さらに本家を真似て、ハイハットも左右に振ってみます。

ドラムのチャンネル9のPANを設定すると、バスドラ、スネアも 振られてしまうので、ハイハットだけを別チャンネルの10に移動します。

  :
#
# Setting
#
sed -e /^#.*/d -e /^$/d > setting.txt <<EOF
delta=0 meta set_tempo n=3 tempo=420000

# Drums
delta=0 prog_num ch=9 v=0
delta=0 ctl_chg ch_vol_msb ch=9 v=100

# HiHat
delta=0 prog_num ch=10 v=0
delta=0 ctl_chg ch_vol_msb ch=10 v=100

# Lead
  :

ハイハットの打ち込み箇所のチャンネル指定を10に変更し、 PAN設定を紛れ込ませておきます。

#
# Hihat-Close
#
$MIDTXT -len > hc.txt <<EOF
delta=0 ctl_chg pan_msb ch=10 v=117
len=1/16 ch=10 note=HC
len=1/16 ch=10 note=R
len=1/16 ch=10 note=HC
delta=0 ctl_chg pan_msb ch=10 v=10
len=1/16 ch=10 note=HC
EOF

最後に注意しないといけないのは、全体の半音上げる設定です。
デフォルト設定では、チャンネル9が無効になってるので、 特に指定しなくても良かったのですが、 チャンネル10もキー・トランスポーズ無効に指定します。

  :
#
# Mix
#
( \
 (cat hc_all.txt | $MIDTXT -tx_abs) ; \
 (cat dr_all.txt | $MIDTXT -tx_abs) ; \
 (cat ld_all.txt | $MIDTXT -tx_abs) ; \
 (cat bs_all.txt | $MIDTXT -tx_abs) ; \
 (cat bk_all.txt | $MIDTXT -tx_abs) ; \
 (cat rt_all.txt | $MIDTXT -tx_abs) ; \
) | sort | $MIDTXT -tx_delta | $MIDTXT -tx_trans 1 -tx_trans_enable 10:0 > note.txt
  :

さらに、イントロとして先頭にハイハット2小節を追加しときます。

そして、どれほど効果があるのか謎な「純正律」を指定 (^_^;

  :
cat hc_4.txt hc_4.txt > intro.txt

cat hdr.txt setting.txt intro.txt note.txt > all.txt

cat all.txt | $MIDTXT -r > ryd.mid
cat ryd.mid | $PROG -nocui -pure -vol $VOL -sox ryd.wav
lame ryd.wav ../ryd-7.mp3

# EOF

最終的な ryd.sh です。

tmp/ に生成された ryd.mid です。

$ ./ryd.sh
$ play tmp/ryd.wav

 ryd-7.mp3 

予想以上に、それらしく聞こえます (^_^v


Photo Musik 打ち込んでみる

「打ち込み」がそれらしく出来るようになってきたので、 他の曲の打ち込みを試してみます。

「Photo Musik」という曲に挑戦してみます。

昔々、坂本教授がNHK-FMでDJをしてた頃、 「電気的音楽講座」とうい特別番組でこの曲のレコーディングの様を紹介してました。
1トラックずつパートを入れいく様子が放送されていたので、 コピーして打ち込みするのにピッタリかも知れません。

この曲は、そのまま坂本教授担当の「サウンドストリート火曜日」のテーマ曲として使われてましたが、 Webで検索してみると「コンピュータおばあちゃん」という曲のB面にも収録されていたようです。

当時は「サウンドストリート火曜日」をエアチェックして、テープが擦り切れる程聞いたものです...
その頃の音源が残っていたので、耳コピーで打ち込みしてみます。


「キッコッコッコッ」から

教授のラジオに習って最初はガイドのクリック音からやってみます。

いわゆる「キッコッコッコッ、カッコッコッコッ」というやつです。

完成したら消す代物ですが、 そもそも「打ち込み」だけで「手弾き」無しなら不要です。
でもそんな事なら、この「打ち込み」自体が不要なのかも...
せっかくなので、クリックから入れてみます。

先の 「打ち込み」してみる で作った ryd.sh を下敷にして、 ptmsk-0.sh というスクリプトファイルにしてみます。

まず最初に、イントロがあってAメロがあって、 といった曲全体の構成を書き出しておきます。

[] の中は、スクリプトファイルの中で扱う名前です。
エンディングは a3, a4 パターンの繰り返しで、 ドラムのフィルが変化しつつフェードアウトです。

ですが面倒なので、とりあえずドラムのフィルは同じパターンの繰り返しにして、 フェードアウトもせずに終了しておきます。

スクリプトファイルでは、 シーケンスの名前と小節の数を、 変数SEQSと変数LENSという配列で持たせるようにします。

ptmsk-0.sh
  :
SEQS=(i0 i1 a0 a1 a2 b c d a1 a2 b c i2 a0 a1 a2 a3 a4 a3 a4 e)
LENS=(2  4  4  4  4  4 5 2 4  4  4 5 4  4  4  4  4  4  4  4  2)
  :

次に、楽器のパートにも名前をつけておいて区別するように作っておきます。

といっても、最初はガイドクリック音のパート1つだけ ;-p)
名前はgcにしておきます。

ptmsk-0.sh
  :
PARTS="gc"
  :

おきまりのHeader生成と、Settingでテンポを設定した後は、 とりあえず全部のパートを休符に設定しておいて、 後から必要な部分を上書きして変更していくようにします。

全部のパートを休符に設定

ptmsk-0.sh
  :
#
# Initialize as all rest
#
$MIDTXT -len > r.1 <<EOF
len=1 ch=0 note=R
EOF

cat r.1 r.1 > r.2
cat r.2 r.2 > r.4

N_1=$(expr ${#SEQS[@]} - 1)
for P in $PARTS ; do
  for i in $(seq 0 $N_1) ; do
    S=${SEQS[$i]}
    L=${LENS[$i]}
    $RM $P.$S
    for j in $(seq $L) ; do
      cat r.1 >> $P.$S
    done
  done
  $RM $P.trans
done
  :

ここではまず、 作業場所 tmp_ptmsk/ ディレクトリで、 r.1 というファイルに1小節分の休符のデータを書き込んでます。

r.1 を 2つ連結して、2小節分の休符のデータを r.2 に、 4小節分の休符のデータを r.4 に書き込んでます。

続いて、forの2重ループでは、 「パートの名前.シーケンスの名前」というファイルに、 そのシーケンスの小節数分の休符のデータ書き込んでます。

そしてようやくガイドクリックのデータです。

#
# Guide Click
#
P=gc
CH=0
CH_VOL=100
PROG=24		# tango accordion

$SED >> setting <<EOF
delta=0 prog_num ch=$CH v=$PROG
delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
EOF

LEN="1/8(1/64)"

$MIDTXT -len > $P.1 <<EOF
len=$LEN ch=$CH note=A6
len=$LEN ch=$CH note=A5
len=$LEN ch=$CH note=A5
len=$LEN ch=$CH note=A5

len=$LEN ch=$CH note=E6
len=$LEN ch=$CH note=A5
len=$LEN ch=$CH note=A5
len=$LEN ch=$CH note=A5
EOF

$MIDTXT -len > $P.2 <<EOF
len=$LEN ch=$CH note=A6
len=$LEN ch=$CH note=E5
len=$LEN ch=$CH note=E6
len=$LEN ch=$CH note=A5

len=$LEN ch=$CH note=A6
len=$LEN ch=$CH note=A5
len=$LEN ch=$CH note=A5
len=$LEN ch=$CH note=R
EOF

cat $P.1 $P.2 > $P.i0
cat $P.1 $P.1 $P.1 $P.1 > $P.i1
cp $P.i1 $P.a0
cp $P.i1 $P.a1
cp $P.i1 $P.a2
cp $P.i1 $P.b
cp $P.i1 $P.c
cat $P.1 >> $P.c
cat $P.1 $P.1 > $P.d
cp $P.i1 $P.i2
cp $P.i1 $P.a3
cp $P.i1 $P.a4
cp $P.i0 $P.e

冒頭で変数を次の内容で設定しておいて、 後続の設定箇所で変数の値を参照するようにしてます。

Settingの箇所で、テンポだけ設定しているファイル setting に対して、 ここでは、パートとしての設定を、ファイル setting に追記します。

音色はprog59のデフォルトの音色の中で、 適当にアタックの速いものを選んでみたので、 オリジナルとは程遠い感じです。
まぁ雰囲気ということで...

そして len= で始まる行が、音符のデータです。
雰囲気で適当に耳コピーしてみました。
gc.1 が「キコココカコココ」で、gc.2 がカウントダウンの部分の音符です。

音符の長さを全部揃えたいので、変数LENに設定して参照してます。
スタッカートの具合を調整したければ、 この箇所の変数LENの値の()の中を変更すれば、全てに反映されます。

最後に「パートの名前.シーケンスの名前」として用意されている休符のデータの入ったファイルの中で、 ガイドクリックのパートのファイルである「gc.シーケンスの名前」のファイルを、 gc.1 や gc.2 の内容に上書きしてます。

ガイドクリックなので、休みの小節は無くて全て書き換えてます。
なのでこのパートでは、休符初期化システムの恩恵にあずかれてません。;-p)

スクリプトファイルの残りの末尾部分

ptmsk-0.sh
  :
#
# Gather and Trans
#
for P in $PARTS ; do
  $RM $P.all
  for S in ${SEQS[@]} ; do
    cat $P.$S >> $P.all
  done
  if [ -e $P.trans ] ; then
    cp $P.all tmp
    cat tmp | $MIDTXT -tx_trans $(cat $P.trans) > $P.all
  fi
done

#
# Mix
#
$RM all
for P in $PARTS ; do
  cat $P.all | $MIDTXT -tx_abs >> all  
done

sort all | $MIDTXT -tx_delta | sed -e '/^delta=0 cmd=f8/d' > note

cat hdr setting note > $SONG.txt

cat $SONG.txt | $MIDTXT -r > $SONG.mid

cat $SONG.mid | $MIDPROG $PROG_OPT -sox $SONG.wav
lame $SONG.wav ../$SONG-$VER.mp3

# EOF

Gather and Trans 箇所はパートとシーケンスの2重ループになっています。
前半では、各パートのシーケンスを集めて「パートの名前.all」というファイルに落します。
後半では、もし「パートの名前.trans」というファイルが存在すれば、 .allファイルに対し midtxt -tx_transコマンドでキー・トランスポーズをかけます。

聞いてみたらそのパートが全体的に高かったので1オクターブ下げたいときなどに、 パートの音符データの最後にでも

echo -12 > $P.tarns

の行を追加しておけば、このGather and Transの箇所でそのパートをまとめて1オクターブ下げてくれます。

Mixの箇所は、 ryd.sh と同様に、各パートを混合する処理です。

といっても、この段階ではパートは1つですが...

ptmsk-0.sh

midi_prog/ ディレクトリがある場所と同じ場所に、 ファイル ptmsk-0.sh を配置して実行します。

$ ./ptmsk-0.sh

「これでガイドが完成しました ;-p)」

ptmsk-0.mp3


続いて「ドミドミシミシミ」

続いて放送でも、音楽として最初に入れたトラックのシーケンス・パターン を入れてみます。

放送で坂本教授が「最初にドミドミシミシミを入れましたね」 とか言ってるやつです。

完成形を聞きなおしてみると、 イントロ、Aメロ、間奏といった箇所でこのパターンが聞こえてます。

上記の箇所にパターンを追加してみます。

ptmsk-0.sh からのパッチです。 ptmsk-1.patch

次の手順でパッチをあてて ptmsk-1.sh を生成します。

$ cp ptmsk-0.sh ptmsk-0.sh.bak   ;;; バックアップをとっておきます
$ mv ptmsk-0.sh ptmsk-1.sh       ;;; 名前を変更して
$ cat ptmsk-1.patch | patch -p0  ;;; パッチをあてます

パッチで更新した箇所

  :
 SONG=ptmsk
-VER=0
+VER=1
 VOL=all:5
 TEMPO=560000	# usec
 
-PARTS="gc"
+PARTS="gc dmsm"
  : 

;;; バージョンを 0 から 1 にして
;;; パートの名前のリストに dmsm を追加
 

  :
+#
+# DomiDomiSimiSimi
+#
+P=dmsm
+CH=3
+CH_VOL=50
+PROG=49		# string ensemble 1
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+EOF
+
  :

;;; パート名 dmsm について
;;; MIDIチャンネル 3
;;; ボリューム 50(最大127)
;;; 音色49
;;; に設定


  :
+LEN="1/16(1/32)"
+
+$MIDTXT -len > $P.1 <<EOF
+len=$LEN ch=$CH note=C5
+len=$LEN ch=$CH note=E5
+EOF
+
+$MIDTXT -len > $P.2 <<EOF
+len=$LEN ch=$CH note=B4
+len=$LEN ch=$CH note=E5
+EOF
  :

;;; 「ドミ」をファイルdmsm.1 に
;;; 「シミ」をファイルdmsm.2 に


  :
+cat $P.1 $P.1 $P.1 $P.1 $P.2 $P.2 $P.2 $P.2 > $P.4
  :

;;; 「ドミドミドミドミシミシミシミシミ」
;;; の1小節分をファイルdmsm.4 に


  :
+cat $P.4 $P.4 $P.4 $P.4 > $P.i1
+cp $P.i1 $P.a0
+cat $P.4 $P.4 $P.4 r.1 > $P.a1
+cp $P.a1 $P.a2
+cp $P.i1 $P.i2
+cp $P.a1 $P.a3
+cp $P.a1 $P.a4
  :

;;; このパートのイントロ、Aメロ部分のファイルに、
;;; 必要な長さ分 dmsm.4 のデータを書き込み

それではスクリプトを実行

$ ./ptmsk-1.sh

「ドミドミシミシミ」が追加されました。

ptmsk-1.mp3


ベース

どんどん行きます。次はベースです。

Aメロ、Bメロ、Cメロがあって、他は休符のようです。
がんばって聞きとってみましたが、サビのCメロの中程がちょっと怪しいかも?

ptmsk-1.sh からのパッチです。 ptmsk-2.patch

パッチで更新した箇所

  :
-VER=1
+VER=2
  :

;;; バージョンを更新


  :
-PARTS="gc dmsm"
+PARTS="gc dmsm bs"
  :

;;; パート名のリストに bs を追加


  :
+#
+# Bass
+#
+P=bs
+CH=1
+CH_VOL=127
+PROG=35		# electric bass (pick)
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+EOF
  :

;;; チャンネル1に設定を追加


  :
+# a0 a1 a2 a3 a4
+$MIDTXT -len > $P.1 <<EOF
+len=1/16 ch=$CH note=A3
+len=1/16 ch=$CH note=A3
+len=5/16 ch=$CH note=R
+len=1/16 ch=$CH note=G3
+len=1/2  ch=$CH note=R
+EOF
+
+$MIDTXT -len > $P.2 <<EOF
+len=1/16 ch=$CH note=F3
+len=1/16 ch=$CH note=F3
+len=5/16 ch=$CH note=R
+len=1/16 ch=$CH note=E3
+len=1/2  ch=$CH note=R
+EOF
  :

;;; Aメロ共通のパターンをファイル bs.1, bs.2 へ


  :
+# a1 a3
+$MIDTXT -len > $P.3 <<EOF
+len=3/8(1/8) ch=$CH note=D3
+len=1/8      ch=$CH note=E3
+len=3/8(1/8) ch=$CH note=F3
+len=1/8      ch=$CH note=D3
+EOF
+
+# a2 a4
+$MIDTXT -len > $P.4 <<EOF
+len=3/16(1/16) ch=$CH note=A3
+len=5/16(1/8)  ch=$CH note=E3
+len=3/16(1/16) ch=$CH note=C4
+len=5/16(1/8)  ch=$CH note=G3
+EOF
  :

;;; Aメロ4小節分の終り部分2パターンをファイル bs.3, bs.4 へ


  :
+# b
+$MIDTXT -len > $P.5 <<EOF
+len=1/8(1/16) ch=$CH note=E3
+len=1/16      ch=$CH note=E3
+len=1/8(1/16) ch=$CH note=E3
+len=1/8(1/16) ch=$CH note=E3
+len=1/16      ch=$CH note=E3
+EOF
  :

;;; Bメロのパターンはこの1小節の繰り返しで ファイル bs.5 へ


  :
+LEN="1/16(1/32)"
+
+# c
+LEN="1/16(1/32)"
+$MIDTXT -len > $P.6 <<EOF
+len=$LEN ch=$CH note=F2
+len=$LEN ch=$CH note=F3
+EOF
+
+cat $P.6 $P.6 $P.6 $P.6 > $P.7			# F
+
+cat $P.7 | $MIDTXT -tx_trans 2 > $P.8		# G
+cat $P.8 | $MIDTXT -tx_trans 2 > $P.9		# A
+cat $P.9 | $MIDTXT -tx_trans 2 > $P.10		# B
+
+cat $P.6 | $MIDTXT -tx_trans 9 > tmp		# D
+cat tmp tmp tmp > $P.11
+cat $P.6 | $MIDTXT -tx_trans 11 >> $P.11	# E
+
+cat $P.7 | $MIDTXT -tx_trans 12 > $P.12		# F
+cat $P.12 | $MIDTXT -tx_trans 2 > $P.13		# G
+cat $P.13 | $MIDTXT -tx_trans 2 > $P.14		# A
  :

;;; 難関のCメロ
;;; 基本は16部音符でオクターブの繰り返しにしてみました

;;; Fの音のパターンを作って bs.6, bs.7 へ
;;; それを元にmidtxtコマンドのキー・トランスポーズで上げていって
;;; bs.8 以降に落してます


  :
+cat $P.1 $P.1 $P.2 $P.1 > $P.a0
+cat $P.1 $P.1 $P.2 $P.3 > $P.a1
+cat $P.1 $P.1 $P.2 $P.4 > $P.a2
+cat $P.5 $P.5 $P.5 $P.5 $P.5 $P.5 $P.5 $P.5 > $P.b
+cat $P.7 $P.8 $P.9 $P.10 $P.11 $P.12 $P.13 $P.13 $P.14 $P.14 > $P.c
+cp $P.a1 $P.a3
+cp $P.a2 $P.a4
  :

;;; bs.1 からbs.14 までのパターンを
;;; Aメロの bs.a0 から bs.a4
;;; Bメロの bs.a0 から bs.b
;;; Cメロの bs.a0 から bs.c
;;; へと振り分けて書き込み

それではパッチをあててスクリプトを実行

$ mv ptmsk-1.sh ptmsk-2.sh
$ cat ptmsk-2.patch | patch -p0
$ ./ptmsk-2.sh

「ベース」が追加されました。

ptmsk-2.mp3


ハイハット

放送2日目に入って、ハイハットの打ち込みです。

打ち込みは単純で、 ガイドクリックと同じく延々同じパターンを繰り返します。

ptmsk-2.sh からのパッチです。 ptmsk-3.patch

パッチで更新した箇所

  :
-VER=2
+VER=3
  :

;;; 毎度お馴染みバージョンの更新


  :
-PARTS="gc dmsm bs"
+PARTS="gc dmsm bs hc"
  :

;;; パート名 hc (Hi-hat Close) 追加


  :
 #
 # Initialize as all rest
 #
-$MIDTXT -len > r.1 <<EOF
-len=1 ch=0 note=R
-EOF
-
-cat r.1 r.1 > r.2
-cat r.2 r.2 > r.4
-
-N_1=$(expr ${#SEQS[@]} - 1)
-for P in $PARTS ; do
-  for i in $(seq 0 $N_1) ; do
-    S=${SEQS[$i]}
-    L=${LENS[$i]}
-    $RM $P.$S
-    for j in $(seq $L) ; do
-      cat r.1 >> $P.$S
+REST_Z=$SONG-$VER-rest.tgz
+if [ -e $REST_Z ] ; then
+  tar xzf $REST_Z
+else
+  echo "len=1 ch=0 note=R" | $MIDTXT -len > r.1
+  cat r.1 r.1 > r.2
+  cat r.2 r.2 > r.4
+
+  LST="r.1 r.2 r.4"
+  N_1=$(expr ${#SEQS[@]} - 1)
+  for P in $PARTS ; do
+    for i in $(seq 0 $N_1) ; do
+      S=${SEQS[$i]}
+      L=${LENS[$i]}
+      $RM $P.$S
+      for j in $(seq $L) ; do
+	cat r.1 >> $P.$S
+      done
+      LST="$LST $P.$S"
     done
+    $RM $P.trans
   done
-  $RM $P.trans
-done
+  tar czf $REST_Z $LST
+fi
  :

;;; ここは、処理速度の改善です

;;; パートが増えてきたので、最初に全部のパートの休符のデータファイルを作るのに、
;;; すごく時間がかかるようになってきました
;;; そこで初回だけファイルを作って、tarでまとめて保存しておきます
;;; 2回目以降は初回で作ったtarファイルを展開するだけにしてます
;;; なので何度も試すときには、多少効果ありです


+#
+# Hi-Hat Close
+#
+P=hc
+CH=10
+CH_VOL=40
+PROG=0		# drum
+PAN=54
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+LEN="1/16(1/64)"
+
+$MIDTXT -len cat > $P.0 <<EOF
+len=$LEN ch=$CH note=HC
+EOF
+
+$RM $P.1
+for i in $(seq 16) ; do
+  cat $P.0 >> $P.1
+done
+
+cat $P.1 $P.1 $P.1 $P.1 > $P.i1
+cp $P.i1 $P.a0
+cp $P.i1 $P.a1
+cp $P.i1 $P.a2
+cp $P.i1 $P.b
+cat $P.1 $P.1 $P.1 $P.1 $P.1 > $P.c
+cat $P.1 $P.1  > $p.d
+cp $P.i1 $P.i2
+cp $P.i1 $P.a3
+cp $P.i1 $P.a4

;;; 使用チャンネルは、一応ドラム用の9とは分けて10にしてます
;;; PANは64がセンターなので少し左に寄せてみました
;;; 16分音符1つのデータを hc.0 に
;;; hc.0 を16個つないで1小節分にしたものを hc1 に
;;; あとはガイドクリックと同じように、
;;; ほぼ全部のシーケンスのファイルに設定

それではパッチをあててスクリプトを実行

$ mv ptmsk-2.sh ptmsk-3.sh
$ cat ptmsk-3.patch | patch -p0
$ ./ptmsk-3.sh

「ハイハット」が追加されました。

ptmsk-3.mp3


ドラム

放送では坂本教授がドラムを叩いてましたが、 ここではスクリプトに「打ち込み」です。

ptmsk-3.sh からのパッチです。 ptmsk-4.patch

パッチで更新した箇所

  :
-VER=3
+VER=4
  :

;;; 毎度のバージョン更新


  :
-PARTS="gc dmsm bs hc"
+PARTS="gc dmsm bs hc dr"
  :

;;; パート名 dr 追加


  :
+#
+# Drums
+#
+P=dr
+CH=9
+CH_VOL=70
+PROG=0		# drum
+PAN=74
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
  :

;;; チャンネル9を使います
;;; PANはハイハットとは逆に少し右に寄せてみました


  :
+# a0 a1 a2 a3 a4
+$MIDTXT -len > $P.1 <<EOF
+len=1/4(1/16)  ch=$CH note=BD1
+len=3/16(1/16) ch=$CH note=SD_El
+len=5/16(1/16) ch=$CH note=BD1
+len=1/4(1/16)  ch=$CH note=SD_El
+EOF
  :

;;; Aメロ全般で使われてる
;;; 3拍目が16分音符1つ喰うパターン


  :
+$MIDTXT -len > $P.f1 <<EOF
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/4(1/16)  ch=$CH note=SD_El
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/8(1/16)  ch=$CH note=SD_El
+len=1/16       ch=$CH note=LowMidTom
+len=1/16       ch=$CH note=SD_El
+EOF
+
+$MIDTXT -len > $P.f2 <<EOF
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/4(1/16)  ch=$CH note=SD_El
+len=3/16(1/16) ch=$CH note=BD1
+len=1/16       ch=$CH note=SD_El
+len=1/16       ch=$CH note=SD_El
+len=1/16       ch=$CH note=SD_El
+len=1/16       ch=$CH note=LowMidTom
+len=1/16       ch=$CH note=LowMidTom
+EOF
  :

;;; Aメロのフィルのパターン
;;; 2つだけ... T_T)


  :
+# b
+$MIDTXT -len > $P.2 <<EOF
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/4(1/16)  ch=$CH note=SD_El
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/16       ch=$CH note=SD_El
+len=3/16(1/16) ch=$CH note=BD1
+EOF
  :

;;; Bメロのパターン
;;; 4拍目でバスドラが16分遅れて入るパターン


  :
+# c
+$MIDTXT -len > $P.3 <<EOF
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/4(1/16)  ch=$CH note=SD_El
+len=1/4(1/16)  ch=$CH note=BD1
+len=1/4(1/16)  ch=$CH note=SD_El
+EOF
  :

;;; Cメロのサビ
;;; いたって普通のドンパン


  :
+# d
+$MIDTXT -len > $P.6 <<EOF
+len=3/16(1/16)  ch=$CH note=BD1
+len=5/16(1/16)  ch=$CH note=SD_El
+len=3/16(1/16)  ch=$CH note=BD1
+len=5/16(1/16)  ch=$CH note=SD_El
+EOF
+
+$MIDTXT -len > $P.7 <<EOF
+len=3/16(1/16)  ch=$CH note=BD1
+len=5/16(1/16)  ch=$CH note=SD_El
+
+len=3/16(1/16)  ch=$CH note=BD1
+len=1/16        ch=$CH note=SD_El
+len=1/16        ch=$CH note=SD_El,BD1
+len=1/16        ch=$CH note=SD_El
+len=1/16        ch=$CH note=SD_El
+len=1/16        ch=$CH note=SD_El
+EOF
  :

;;; 間奏のつっかかるようなパターンとフィル
;;; かっちょいい


  :
+cat $P.1 $P.1 $P.1 $P.1 > $P.a0
+cat $P.1 $P.1 $P.1 $P.f1 > $P.a1
+cat $P.1 $P.1 $P.1 $P.f2 > $P.a2
+cat $P.2 $P.2 $P.2 $P.2 > $P.b
+cat $P.3 $P.3 $P.3 $P.3 $P.3 > $P.c
+cat $P.6 $P.7 > $P.d
+cat $P.a0 > $P.i2
+cp $P.a1 $P.a3
+cp $P.a2 $P.a4
  :

;;; 例によって小節のパターンを「dr.番号」のファイルにして
;;; 「dr.シーケンス名」のファイルに振り分けてます

それではパッチをあててスクリプトを実行

$ mv ptmsk-3.sh ptmsk-4.sh
$ cat ptmsk-4.patch | patch -p0
$ ./ptmsk-4.sh

難関のドラム総突破して「ドラム」が追加されました。(^^v

ptmsk-4.mp3


リード

放送ではドラムの次は「ブラスっぽい音」を入れてたはずですが、 ここは耳コピーの都合で、より簡単そうな主旋律のメロディからにします。

Aメロ、Bメロ、Cメロと、チャンネルを変えて音色も変えてみました。
(と言っても、prog59のデフォルトの音色は乏しく、基本的に3つくらいの音色でやりくりしてますorz)

耳コピーでCメロがよく聞きとれません。
と言うか、音は聞こえてるのですが、その音がどの鍵盤の音になるのかがよく判別できないです。

ptmsk-4.sh からのパッチです。 ptmsk-5.patch

パッチで更新した箇所

  :
-VER=4
+VER=5
  :

;;; ついにバージョン5


  :
-PARTS="gc dmsm bs hc dr"
+PARTS="gc dmsm bs hc dr lead_a lead_b lead_c"
  :

;;; Aメロ、Bメロ、Cメロ
;;; 同時に演奏する事はないですが
;;; 楽器のパートを分けて、チャンネルも別にしてみました


  :
+#
+# Lead (a)
+#
+P=lead_a
+CH=4
+CH_VOL=30
+PROG=28		# electric guitar (clean)
+PAN=84
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+# a0 a1 a2 a3 a4
+$MIDTXT -len > $P.1 <<EOF
+len=1/2(1/4) ch=$CH note=E5
+len=7/16(1/4) ch=$CH note=G5
+len=1/16 ch=$CH note=F5
+EOF
+
+# a1 a3
+$MIDTXT -len > $P.2 <<EOF
+len=3/16 ch=$CH note=E5
+len=5/16 ch=$CH note=D5
+len=3/16 ch=$CH note=E5
+len=5/16 ch=$CH note=F5
+EOF
+
+# a2 a4
+$MIDTXT -len > $P.3 <<EOF
+len=3/16 ch=$CH note=D5
+len=5/16 ch=$CH note=E5
+len=3/16 ch=$CH note=F5
+len=5/16 ch=$CH note=G5
+EOF
+
+cat $P.1 $P.1 $P.1 $P.1 > $P.a0
+cat $P.1 $P.1 $P.1 $P.2 > $P.a1
+cat $P.1 $P.1 $P.1 $P.3 > $P.a2
+cp $P.a1 $P.a3
+cp $P.a2 $P.a4
  :

;;; まずAメロ
;;; パターンは3つだけ
;;; 1小節とごのデータをファイルlead_a.1, lead_a.2, lead_a.3 に落して
;;; シーケンスのデータをファイルlead_a.a0からlead_a.a4 に


  :
+#
+# Lead (b)
+#
+P=lead_b
+CH=5
+CH_VOL=30
+PROG=50		# strings ensamble 2
+PAN=44
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+# b
+$MIDTXT -len > $P.4 <<EOF
+len=1/4 ch=$CH note=B5
+len=1/4 ch=$CH note=G5
+len=1/4 ch=$CH note=B5
+len=1/4 ch=$CH note=C6
+EOF
+
+cat $P.4 $P.4 $P.4 $P.4 > $P.b
  :

;;; 続いてBメロ
;;; 単純に1つのパターンの繰り返し


  :
+#
+# Lead (c)
+#
+P=lead_c
+CH=6
+CH_VOL=60
+PROG=51		# synthstrings 1
+PAN=84
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+# c
+$MIDTXT -len > $P.9 <<EOF
+len=3/8  ch=$CH note=C5
+len=1/16 ch=$CH note=B4
+len=1/16 ch=$CH note=A4
+len=3/16 ch=$CH note=G4
+len=5/16 ch=$CH note=C4
+EOF
+
+$MIDTXT -len > $P.10 <<EOF
+len=3/8  ch=$CH note=F5
+len=1/16 ch=$CH note=E5
+len=1/16 ch=$CH note=D5
+len=3/16 ch=$CH note=C5
+len=3/16 ch=$CH note=A4
+len=1/8  ch=$CH note=C5
+EOF
+
+$MIDTXT -len > $P.12 <<EOF
+len=1    ch=$CH note=D5
+EOF
+
+$MIDTXT -len > $P.13 <<EOF
+len=1    ch=$CH note=E5
+EOF
+
+cat $P.9 $P.9 $P.10 $P.12 $P.13 > $P.c
  :

;;; そして難所のCメロ
;;; 耳コピーは果たしてこれであってるのだろうか?

それではパッチをあててスクリプトを実行

$ mv ptmsk-4.sh ptmsk-5.sh
$ cat ptmsk-5.patch | patch -p0
$ ./ptmsk-5.sh

主旋律が入るとぐっと雰囲気が出てきました。
でもCメロのサビがなんか違うような...

ptmsk-5.mp3


リード2

主旋律をAメロ、Bメロ、Cメロとパートを分けてましたが、 Aメロ、Bメロは同じ音色で統一して同じパートにまとめるよう変更しました。

スクリプトのパート名lead_a, lead_b をまとめて、leadに変更し、 lead_c を lead2 に変更してます。

AメロのPANは右寄り、BメロのPANはセンター、CメロのPANは左寄りに変えてみました。

そして最後に出て来るAメロ(その3)、Aメロ(その4)のメロディを追加。
これまた耳コピーがこれで合ってるのやら...?

ptmsk-5.sh からのパッチです。 ptmsk-6.patch

パッチで更新した箇所

  :
-VER=5
+VER=6
  :
;;; バージョン6に


  :
-PARTS="gc dmsm bs hc dr lead_a lead_b lead_c"
+PARTS="gc dmsm bs hc dr lead lead2"
  :
;;; leadのパート名を変更


  :
-# Lead (a)
+# Lead (a, b)
 #
-P=lead_a
+P=lead
  :
;;; lead_a, lead_b を lead に
;;; 音符データの統合内容は省略


  :
-cat $P.4 $P.4 $P.4 $P.4 > $P.b
+echo "delta=0 ctl_chg pan_msb ch=$CH v=64" > $P.b
+cat $P.4 $P.4 $P.4 $P.4 >> $P.b
+echo "delta=0 ctl_chg pan_msb ch=$CH v=$PAN" >> $P.b
  :
;;; BメロのPANをセンターに
;;; Bメロの末尾でPANを元に戻す


  :
-# Lead (c)
+# Lead2 (c, a3, a4)
 #
-P=lead_c
+P=lead2
 CH=6
-CH_VOL=60
+CH_VOL=50
 PROG=51		# synthstrings 1
-PAN=84
+PAN=44
  :
;;; lead_c を lead2 に
;;; 音量を若干調整
;;; PANは左寄に変更


  :
+# a3
+$MIDTXT -len > $P.1 <<EOF
+len=1/4  ch=$CH note=E6
+len=3/16 ch=$CH note=A6
+len=1/16 ch=$CH note=B6
+len=1/16 ch=$CH note=C7
+len=1/16 ch=$CH note=B6
+len=1/16 ch=$CH note=A6
+len=5/16 ch=$CH note=G6
+EOF
+
+$MIDTXT -len > $P.2 <<EOF
+len=1/4  ch=$CH note=E6
+len=3/16 ch=$CH note=A6
+len=1/16 ch=$CH note=C7
+len=1/8  ch=$CH note=G6
+len=1/16 ch=$CH note=C6
+len=1/8  ch=$CH note=G6
+len=1/16 ch=$CH note=F6
+len=1/16 ch=$CH note=E6
+len=1/16 ch=$CH note=G6
+EOF
+
+$MIDTXT -len > $P.3 <<EOF
+len=3/16 ch=$CH note=E6
+len=5/16 ch=$CH note=D6
+len=3/16 ch=$CH note=E6
+len=5/16 ch=$CH note=F6
+EOF
+
+cat $P.1 $P.1 $P.2 $P.3 > $P.a3
+cp $P.a3 $P.a4
  :
;;; そして追加したメロディー
;;; 合ってるかどうか謎...

それではパッチをあててスクリプトを実行

$ mv ptmsk-5.sh ptmsk-6.sh
$ cat ptmsk-6.patch | patch -p0
$ ./ptmsk-6.sh

ptmsk-6.mp3

追加した後半部分が合ってるか変更と確認を繰り返す際は、 スクリプトの冒頭のシーケンス定義を変更して試します。

  :
#SEQS=(i0 i1 a0 a1 a2 b c d a1 a2 b c i2 a0 a1 a2 a3 a4 a3 a4 e)
#LENS=(2  4  4  4  4  4 5 2 4  4  4 5 4  4  4  4  4  4  4  4  2)
SEQS=(i0 a3 a4 a3 a4 e)
LENS=(2  4  4  4  4  2)
  :

この変更でAメロ(その3)、Aメロ(その4)だけの演奏になります。

確認したいパートだけ再生するには、いくつか方法があります。

各パートの冒頭でチャンネルボリュームを設定してるので、 ここで不要なパートのボリュームを0にすれば可能です。

  :
#
# Guide Click
#
P=gc
CH=0
#CH_VOL=100
CH_VOL=0
PROG=24		# tango accordion
  :

この変更でガイドクリックのパートの音量が0になります。

ですがこの方法では、prog59の処理としては波形を生成する重たい処理を実行して、 最後に音量を0にしてるので波形生成の負荷はあまり変わりません。

それよりもスクリプト冒頭の調整用ボリューム設定で変更する方法がベターです。

  :
SONG=ptmsk
VER=6
#VOL=all:5
VOL=all:5,0:0
TEMPO=560000	# usec
  :

この変更でもチャンネル0のボリュームは0になりますが、 prog59ではこの場合ノートON/OFFの登録処理をしないので、 波形生成処理の負荷はその分軽減されます。

この場合それでも、不要なチャンネル音符データが.midファイルに含まれていて、 prog59でデータを読込んで判定されて「ノートON/OFFしない」という処理がなされます。

はなから.midファイルにデータを入れないようにするには、 パート名のリストからパート名を外すのが手っ取り早いです。

  :
#PARTS="gc dmsm bs hc dr lead lead2"
PARTS="dmsm bs hc dr lead lead2"
  :

この変更で、パート名gcのガイドクリックは存在しなくなるので、 .midファイルにも含まれません。


ブラス

ここで後回しにして先伸ばしにしてたブラスのパートのコピーに挑戦。

prog59の音色が適当なので「これ」と言った物がなく、とりあえず適当に選んでます。

Aメロ1,2,3小節のパターンのコードは Am, FM7 で問題なさげなので、 ブラスのパターンも「もろ」にAmにしてみました。

Aメロ4小節目のコードが苦労しました。
ベースとリードのパターンから例の表の方式で悩んでみました。


Aメロ(その1,3) 4小節目

bass	lead	すなお	ひねり	合わせ技
----    ----	------	------	--------
D	E		Em7	Em7
	D	Dm		Dm7
E		Em7
F	E	FM7		FM7
	F	F		Dm7 or G7 悩んでG7
D		Dm


Aメロ(その1,3) 4小節目
bass	lead	すなお	ひねり	合わせ技
----    ----	------	------	--------
A	D		Dm	Dm7 (なんとなく7thか)
E	E	Em		Em7 (なんとなく7thか)
C	F		F	FM7 (なんとなく7thか)
G	G	G		G7 or CM7, Em7, Am7 (7thでこれだけの候補)
				試してみてCM7か?

Cメロの末尾は独特なので、たぶんsus4からメジャーのパターン。

ptmsk-6.sh からのパッチです。 ptmsk-7.patch

パッチで更新した箇所

  :
-VER=6
+VER=7
  :
;;; バージョン7


  :
-PARTS="gc dmsm bs hc dr lead lead2"
+PARTS="gc dmsm bs hc dr lead lead2 br"
  :
;;; パート名br追加 

 
  :
+$MIDTXT -len > $P.4 <<EOF
+len=3/16 ch=$CH note=D6
+len=5/16 ch=$CH note=E6
+len=3/16 ch=$CH note=F6
+len=5/16 ch=$CH note=G6
+EOF
+
 cat $P.1 $P.1 $P.2 $P.3 > $P.a3
-cp $P.a3 $P.a4
+cat $P.1 $P.1 $P.2 $P.4 > $P.a4
  :
;;; パートlead2のAメロ(その4)の末尾を修正


  :
+#
+# Brass
+#
+P=br
+CH=7
+CH_VOL=30
+#PROG=62		# brass section
+PROG=28		# electric guitar (clean)
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+EOF
+
+# a1 a2 a3 a4
+$MIDTXT -len > $P.1 <<EOF
+len=1/8  ch=$CH note=R
+len=1/16 ch=$CH note=A5,C5,E5	;;; Am
+len=1/16 ch=$CH note=A5,C5,E5	;;; Am
+len=3/4  ch=$CH note=R
+EOF
+
+# a1 a3
+$MIDTXT -len > $P.2 <<EOF
+len=3/16 ch=$CH note=G5,B5,D6,E6	;;; Em7
+len=5/16 ch=$CH note=F5,A5,C6,D6	;;; Dm7
+len=3/16 ch=$CH note=F5,A5,C6,E6	;;; FM7
+len=5/16 ch=$CH note=G5,B5,D6,F6	;;; G7
+EOF
+
+# a2 a4
+$MIDTXT -len > $P.3 <<EOF
+len=3/16 ch=$CH note=F5,A5,C6,D6	;;; Dm7
+len=5/16 ch=$CH note=G5,B5,D6,E6	;;; Em7
+len=3/16 ch=$CH note=A5,C6,E6,F6	;;; FM7
+len=5/16 ch=$CH note=B5,C6,E6,G6	;;; CM7
+EOF
+
+# c
+$MIDTXT -len > $P.8 <<EOF
+len=1/2 ch=$CH note=G5,C6,D6		;;; Gsus4
+len=1/2 ch=$CH note=G5,B5,D6		;;; G
+len=1/2 ch=$CH note=A5,D6,E6		;;; Asus4
+len=1/2 ch=$CH note=A5,C#6,E6		;;; A
+EOF
+
+cat $P.1 $P.1 $P.1 $P.2 > $P.a1
+cat $P.1 $P.1 $P.1 $P.3 > $P.a2
+cat r.1 r.1 r.1 $P.8 > $P.c
+cp $P.a1 $P.a3
+cp $P.a2 $P.a4
  :
;;; ブラスのパートを追加
;;; 果たしてコード合ってるか?

それではパッチをあててスクリプトを実行

$ mv ptmsk-6.sh ptmsk-7.sh
$ cat ptmsk-7.patch | patch -p0
$ ./ptmsk-7.sh

ptmsk-7.mp3


MIDIファイルのテキスト変換 (その4) コード

ここらで一息入れて Tea break。

教授の放送では曲がかかってましたが、 ここではツールのプログラムの更新です。;-p)

midtxtコマンドで和音(コード)の入力部分が少々面倒です。

  :
# a1 a3
$MIDTXT -len > $P.2 <<EOF
len=3/16 ch=$CH note=G5,B5,D6,E6	;;; Em7
len=5/16 ch=$CH note=F5,A5,C6,D6	;;; Dm7
len=3/16 ch=$CH note=F5,A5,C6,E6	;;; FM7
len=5/16 ch=$CH note=G5,B5,D6,F6	;;; G7
EOF
  :

note=G5,B5,D6,E6 の箇所を chord=Em7/G5 と書けるよう midtxtコマンドを機能追加してみます。

chordの指定は次の形式とします。

chord=コードの名前/一番低いnoteの指定

さらに ch=$CH の指定も冗長なので、省略可能に変更します。
省略された場合は、midtxtコマンドの起動時の引数として指定された値を使う事にします。
起動時の引数も省略されたときは、デフォルトの値を0チャンネルとします。

どうせならベロシティの値も今の省略時100扱いから、 midtxtコマンドの起動時の引数として指定された値を使うように変更します。
そして起動時の引数も省略されたときは、デフォルトの値を100にして従来互換 ;-p)

midtxt4.patch

機能追加の確認のため、まず従来の方式で生成した.midファイルを保存しておきます。

$ rm -rf tmp_ptmsk
$ cat ptmsk-7.sh | sed -e '/^cat \$SONG.mid/d' -e '/^lame/d' | bash
$ mv tmp_ptmsk tmp_ptmsk.bak

パッチをあててmidtxtコマンドを更新します

$ cat midtxt4.patch | ( cd midi_prog ; patch -p1 ; make midtxt )

ptmsk-7.sh と同じ内容で、midtxtに追加したchord機能などを使うようにした ptmsk-8.sh を用意します。

ptmsk-7.sh からのパッチです。 ptmsk-8.patch

次の箇所

  :
 # a1 a2 a3 a4
-$MIDTXT -len > $P.1 <<EOF
-len=1/8  ch=$CH note=R
-len=1/16 ch=$CH note=A5,C5,E5	;;; Am
-len=1/16 ch=$CH note=A5,C5,E5	;;; Am
-len=3/4  ch=$CH note=R
+$MIDTXT -len -ch $CH > $P.1 <<EOF
+len=1/8  note=R
+#len=1/16 chord=Am/C5
+#len=1/16 chord=Am/C5
+len=1/16 note=A5,C5,E5	;;; Am
+len=1/16 note=A5,C5,E5	;;; Am
+len=3/4  note=R
 EOF
  :

ここについては ptmsk-7.sh での和音指定時の並びが、音の低い順になっていませんでした。
なので、チャンネルの指定だけ変更してchordしては従来のままnote指定にしてます。;-p)

パッチをあてて ptmsk-8.sh を生成します。

$ cp ptmsk-7.sh ptmsk-7.sh.bak
$ mv ptmsk-7.sh ptmsk-8.sh
$ cat ptmsk-8.patch | patch -p0

スクリプトの内容の末尾部分を削除しつつ実行して .midファイルの生成までを行ないます。

$ cat ptmsk-8.sh | sed -e '/^cat \$SONG.mid/d' -e '/^lame/d' | bash

従来方式で生成した作業ディレクトリと、 新しい方式で生成した作業ディレクトリを比較します。

$ diff -urN tmp_ptmsk.bak tmp_ptmsk
Binary files tmp_ptmsk.bak/ptmsk-7-rest.tgz and tmp_ptmsk/ptmsk-7-rest.tgz differ
Binary files tmp_ptmsk.bak/ptmsk-8-rest.tgz and tmp_ptmsk/ptmsk-8-rest.tgz differ
$ 

バージョンを7から8に上げた影響で、 ファイルptmsk-7-rest.tgzとファイルptmsk-8-rest.tgzの違いが検出されましたが、 それ以外は、.midファイルも含めて一致。OK


バッキングのコード

コードの入力がしやすくなったので、バッキングのコードを探りつつ追加してみます。

Cメロは、かなり重厚な感じなので、AメロBメロとは音量の設定を変えるために、別のチャンネルに分けてみました。
それでもなかなかオリジナルの雰囲気に近づけません。
コードが違う可能性大です。

Lead2のパートがどうもしっくりこなかったので、ちょっとだけ修正。

ptmsk-8.sh からのパッチです。 ptmsk-9.patch

パッチで更新した箇所

  :
-VER=8
+VER=9
  :
;;; バージョン9
 

  :
-PARTS="gc dmsm bs hc dr lead lead2 br"
+PARTS="gc dmsm bs hc dr lead lead2 br bk bk_c"
  :
;;; bk と bk_c 追加
 

  :
 len=1/16 note=E5
 len=1/16 note=D5
 len=3/16 note=C5
-len=3/16 note=A4
+len=3/16 note=F4
 len=1/8  note=C5
 EOF
  : 
;;; パートLead2 の Cメロ3小節目
;;; しっくりこなかったので修正


 len=1/16 note=C6
 len=1/8  note=G6
 len=1/16 note=F6
-len=1/16 note=E6
-len=1/16 note=G6
+len=1/8  note=E6
 EOF
  :
;;; パートLead2 の Aメロ(その3,4)3小節目
;;; しっくりこなかったので修正

 
  :
 len=1/8  note=R
-#len=1/16 chord=Am/C5
-#len=1/16 chord=Am/C5
-len=1/16 note=A5,C5,E5	;;; Am
-len=1/16 note=A5,C5,E5	;;; Am
+len=1/16 chord=Am/E5
+len=1/16 chord=Am/E5
 len=3/4  note=R
 EOF
  :
;;; chord方式に修正
 

  :
+#
+# Backing Chord
+#
+P=bk
+CH=8
+CH_VOL=20
+PROG=49		# string ensemble 1
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+EOF
+
+# a0 a1 a2 a3 a4
+$MIDTXT -len -ch $CH > $P.1 <<EOF
+len=1/16 chord=Am/A6
+len=1/16 chord=Am/A6
+len=5/16 note=R
+len=1/16 chord=Em7/G6
+len=1/2  note=R
+EOF
+
+$MIDTXT -len -ch $CH > $P.2 <<EOF
+len=1/16 chord=FM7/A6
+len=1/16 chord=FM7/A6
+len=5/16 note=R
+len=1/16 chord=Em7/G6
+len=1/2  note=R
+EOF
+
+# b
+$MIDTXT -len -ch $CH > $P.3 <<EOF
+len=13/16 chord=CM7/C6
+len=3/16  chord=FM7/E6
+EOF
+
+cat $P.1 $P.1 $P.2 $P.2 > $P.i1
+cp $P.i1 $P.a0
+cat $P.1 $P.1 $P.2 r.1 > $P.a1
+cp $P.a1 $P.a2
+cp $P.a1 $P.a3
+cp $P.a1 $P.a4
+cat $P.3 $P.3 $P.3 $P.3 > $P.b
  :
;;; Aメロ、Bメロのバッキングコード追加


  :
+#
+# Backing Chord (c)
+#
+P=bk
+CH=11
+CH_VOL=40
+PROG=28		# electric guitar (clean)
+PAN=84
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+# c
+$MIDTXT -len -ch $CH > $P.c <<EOF
+len=1/2 chord=F/F3
+len=1/2 chord=G/G3
+len=1/2 chord=F/A3
+len=1/2 chord=G/B3
+
+len=3/8 chord=Dm/F4
+len=1/8 chord=Em/G4
+len=1/2 chord=FM7/A4
+
+len=1/2 chord=Gsus4/G4
+len=1/2 chord=G/G4
+len=1/2 chord=Asus4/A4
+len=1/2 chord=A/A4
+EOF
  :
;;; Cメロのバッキングコード追加

それではパッチをあててスクリプトを実行

$ mv ptmsk-8.sh ptmsk-9.sh
$ cat ptmsk-9.patch | patch -p0
$ ./ptmsk-9.sh

ptmsk-9.mp3


リードの音を変更

Aメロ、Bメロのリードに設定してるprog59のデフォルトの音色の「のっぺり感」が目立ち、 あまりにもオリジナルに程遠いので、もうちょっとましな音色を作ってみます。

ptmsk-9 からのパッチです。 ptmsk-10.patch

パッチで更新した箇所

  :
-VER=9
+VER=10
  :
;;; ついに2桁


  :
 PROG_OPT="-nocui -pure -vol $VOL"
+TONE=../$SONG-$VER.tone
  :
;;; 作業場所からの音色load用のファイルパス名は
;;; ../ptmsk-10.tone


  :
 P=bs
 CH=1
-CH_VOL=127
+CH_VOL=110
  :
;;; ベースの音がMAXで大きすぎたので修正


  :
 # c
 LEN="1/16(1/32)"
 $MIDTXT -len -ch $CH > $P.6 <<EOF
-len=$LEN note=F2
-len=$LEN note=F3
+len=$LEN note=F2,F3
+len=$LEN note=F2,F3
 EOF
  :
;;; ベースのCメロのオクターブ交互パターンは、どうも違うみたいなので
;;; 同時押しに変更


  :
 P=lead
 CH=4
 CH_VOL=30
-PROG=28		# electric guitar (clean)
+PROG=81		# Lead 1 (square) : ptmsk_lead
 PAN=84
  :
;;; Aメロ、Bメロのリードの音を、新しく作ったものに変更


  :
 len=3/16 chord=Dm7/F5
 len=5/16 chord=Em7/G5
 len=3/16 chord=FM7/A5
-len=5/16 chord=CM7/B5
+len=5/16 chord=Em/B5
 EOF
  :
;;; Aメロ(その2,4)のブラスのパート4小節目の4拍目
;;; ここも違ったようなので素直なEmに変更


  :
 #
 # Backing Chord (c)
 #
-P=bk
+P=bk_c
  :
;;; 前回の変更漏れを修正


  :
 cat $SONG.txt | $MIDTXT -r > $SONG.mid
 
-cat $SONG.mid | $MIDPROG $PROG_OPT -sox $SONG.wav
+if [ -e $TONE ] ; then
+  (echo load ; cat $TONE $SONG.mid ) | $MIDPROG $PROG_OPT -sox $SONG.wav
+else
+  cat $SONG.mid | $MIDPROG $PROG_OPT -sox $SONG.wav
+fi
 lame $SONG.wav ../$SONG-$VER.mp3
 
 # EOF

;;; スクリプトファイルの最後で、生成した.midファイルをprog59コマンドに与える箇所
;;; 音色load用のファイル(ここでは ptmsk-10.tone)が存在した場合は、
;;; prog59コマンド音色をloadしてから、.midファイルを流し込むように変更


  :
--- ptmsk-0.tone	Mon Jun 22 23:00:00 2015
+++ ptmsk-10.tone	Tue Jun 23 00:00:00 2015
@@ -172,6 +172,49 @@
     gain 0.000000
   chorus 0
 ,
+  name "ptmsk_lead"
+  vco
+    wave1 2
+    wave2 2
  :
+  delay
+    onoff 0
+    sec 0
+    gain 0
+  chorus 1
+,
  :
;;; patchファイルは未だ続きます

;;; 音色load用のファイルの、デフォルト設定からのパッチになります
;;; まず、ptmsk_leadという名前のtoneデータを追加


  :
   tone_compo
   {
-    name "lead"
+    name "ptmsk_lead"
     note -1
     rate 1.000000
   ,
  :
;;; プログラム番号81番 "Lead 1 (square)" に
;;; ptmsk_leadのtoneデータを登録

デフォルトの音色のファイルを用意してパッチをあててスクリプトを実行してみます。

$ echo save | midi_prog/prog59 > ptmsk-10.tone

;;; prog59 のデフォルトの音色データは ptmsk-10.tone に保存


$ mv ptmsk-9.sh ptmsk-10.sh
$ cat ptmsk-10.patch | patch -p0

;;; パッチをあてて、ptmsk-10.tone, ptmsk-10.sh の内容を更新


$ ./ptmsk-10.sh

;;; スクリプトを実行

ptmsk-10.mp3


キカコ

パーカッションの「キカコ」音を追加してみます。

さらに、Bメロのバッキングコードの音も、新たに作って追加します。
「グブララーン」と変化する音です。(わかってもらえるだろうか?)

ローパスフィルタのカットオフ周波数をLFOのサイン波で揺さぶって、音の明るさを変化させます。

ついでに「チュチュン」音も追加。

ptmsk-10 からのパッチです。 ptmsk-11.patch

パッチで更新した箇所

--- ptmsk-10.sh	Tue Jun 23 00:00:00 2015
+++ ptmsk-11.sh	Tue Jun 23 02:00:00 2015
  :
-VER=10
+VER=11
  :
;;; バージョン11


  :
-PARTS="gc dmsm bs hc dr lead lead2 br bk bk_c"
+PARTS="gc dmsm bs hc dr lead lead2 br bk bk_b bk_c kikako tyun"
  :
;;; バッキングコード bk からBメロを bk_b に分離
;;; パーカッション kikako と tyun を追加


  :
<省略>
  :
+#
+# Backing Chord (b)
+#
+P=bk_b
+CH=13
+CH_VOL=20
+PROG=52		# SynthStrings 2 : ptmsk_b
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+EOF
+
+# b
+$MIDTXT -len -ch $CH > $P.3 <<EOF
+len=29/16 chord=CM7/C6
+len=3/16  chord=FM7/E6
+EOF
+
+cat $P.3 $P.3 > $P.b
  :
;;; bk から Bメロを bk_b に分離
;;; 音色を追加した ptmsk_b に変更
;;; 音符のデータも変更少々
 

  :
+#
+# Percussion (kikako)
+#
+P=kikako
+CH=12
+CH_VOL=10
+PROG=124	# Bird Tweet : ptmsk_kikako
+PAN=94
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+$MIDTXT -len -ch $CH > $P.1 <<EOF
+len=9/16 note=R
+len=1/16 note=A7
+len=1/16 note=A8
+len=1/16 note=A7
+len=1/4  note=R
+EOF
+
+cat r.1 $P.1 > $P.i0
+cat $P.1 $P.1 $P.1 $P.1 > $P.i1
+cp $P.i1 $P.a0
+cp $P.i1 $P.a1
+cp $P.i1 $P.a2
+cp $P.i1 $P.a3
+cp $P.i1 $P.a4
+cp $P.i1 $P.b
+cp $P.i1 $P.i2
+cp $P.i1 $P.c
+cat $P.1 >> $P.c
+cat $P.1 $P.1 > $P.d
  :
;;; パーカッション「キカコ」追加


  :
+#
+# Percussion (tyun)
+#
+P=tyun
+CH=14
+CH_VOL=50
+PROG=119	# Synth Drum : ptmsk_tyun
+PAN=34
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+$MIDTXT -len -ch $CH > $P.1 <<EOF
+len=1/16  note=A7
+len=14/16 note=R
+len=1/16  note=A7
+EOF
+
+cat $P.1 $P.1 $P.1 $P.1 > $P.i1
+cp $P.i1 $P.a0
+cp $P.i1 $P.a1
+cp $P.i1 $P.a2
+cp $P.i1 $P.a3
+cp $P.i1 $P.a4
+cp $P.i1 $P.b
+cp $P.i1 $P.i2
+cp $P.i1 $P.c
+cat $P.1 >> $P.c
+cat $P.1 $P.1 > $P.d
  :
;;; パーカッション「チュチュン」追加

 
--- ptmsk-10.tone	Tue Jun 23 00:00:00 2015
+++ ptmsk-11.tone	Tue Jun 23 02:00:00 2015
@@ -215,6 +215,135 @@
     gain 0
   chorus 1
 ,
+  name "ptmsk_kikako"
+  vco
+    wave1 0
+    wave2 2
  :
;;; 音色のデータファイルに
;;; 「キカコ」、Bメロ用の「グブララーン」音、「チュチュン」を追加

パッチをあててスクリプトと音色のファイルを更新し、実行してみます。

$ mv ptmsk-10.tone ptmsk-11.tone
$ mv ptmsk-10.sh ptmsk-11.sh
$ cat ptmsk-11.patch | patch -p0
$ ./ptmsk-11.sh

ptmsk-11.mp3

ようやくここまで来ました。


ブラスの音を変更

ブラスの音がテキトーだったので作り直してみます。

さらに放送で教授がやってたように、16分音符くらいのディレイで右から左に飛ばすようにしてみます。
別のチャンネルで同じデータを16分音符だけ遅らせて、PANで振り分けてみます。

ついでに「ドミドミシミシミ」のパートもPANで左右に振ってみます。

ptmsk-11 からのパッチです。 ptmsk-12.patch

パッチで更新した箇所

  :
-VER=11
+VER=12
  :
;;; バージョン12


  : 
-PARTS="gc dmsm bs hc dr lead lead2 br bk bk_b bk_c kikako tyun"
+PARTS="gc dmsm bs hc dr lead lead2 br br2 bk bk_b bk_c kikako tyun"
  :
;;; ブラスのエコー用の br2 追加

 
  :
 LEN="1/16(1/32)"
 
 $MIDTXT -len -ch $CH > $P.1 <<EOF
+delta=0 ctl_chg pan_msb ch=$CH v=0
 len=$LEN note=C5
+delta=0 ctl_chg pan_msb ch=$CH v=127
 len=$LEN note=E5
 EOF
 
 $MIDTXT -len -ch $CH > $P.2 <<EOF
+delta=0 ctl_chg pan_msb ch=$CH v=0
 len=$LEN note=B4
+delta=0 ctl_chg pan_msb ch=$CH v=127
 len=$LEN note=E5
 EOF
  :
;;; 「ドミドミシミシミ」を左右に振ります


  :
 P=br
 CH=7
-CH_VOL=30
-#PROG=62		# brass section
-PROG=28		# electric guitar (clean)
+CH_VOL=20
+PROG=62		# brass section
+PAN=127
 
 $SED >> setting <<EOF
 delta=0 prog_num ch=$CH v=$PROG
 delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
 EOF
  :
;;; ブラスの音色のプログラム番号を変更しつつ
;;; PAN設定追加で右側へ
 

  :
 P=tyun
 CH=14
-CH_VOL=50
+CH_VOL=30
 PROG=119	# Synth Drum : ptmsk_tyun
 PAN=34
  :
;;; 前回追加の「チュチュン」がうるさ過ぎたのでボリュームを下げます
 

  :
 #
+# Brass 2 (echo)
+#
+P=br2
+CH=2
+CH_VOL=20
+PROG=62		# brass section
+PAN=0
+
+$SED >> setting <<EOF
+delta=0 prog_num ch=$CH v=$PROG
+delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
+delta=0 ctl_chg pan_msb ch=$CH v=$PAN
+EOF
+
+$MIDTXT -len -ch $CH > $P.all <<EOF
+len=1/16 note=R
+EOF
+cat br.all | sed -e s/ch=7/ch=$CH/ >> $P.all
  :
;;; Gather と Mix の間で、細工をごにょごにょ
;;; ブラスのエコーを追加
;;; オリジナルのブラスをコピーして、先頭に16分音符の休符を挿入
;;; さらにチャンネル指定の箇所を、エコー側のチャンネルに置換


  :
--- ptmsk-11.tone	2015-06-23 02:00:00.000000000 +0900
+++ ptmsk-12.tone	2015-06-23 23:00:00.000000000 +0900
@@ -215,6 +215,49 @@
     gain 0
   chorus 1
 ,
+  name "ptmsk_brass"
+  vco
+    wave1 2
+    wave2 2
  :
;;; 以降は作り直して追加したブラスの音色データ

パッチをあててスクリプトと音色のファイルを更新し、実行してみます。

$ mv ptmsk-11.tone ptmsk-12.tone
$ mv ptmsk-11.sh ptmsk-12.sh
$ cat ptmsk-12.patch | patch -p0
$ ./ptmsk-12.sh

ptmsk-12.mp3


仕上げ

ここまで来れば、毒皿で音色を色々と変更してみます。

サビのCメロで重厚なストリングスを再現したかったのですが、 素人にはなかなか難しい...

そして、最後にガイドの「キココ」を外しておきます。

ptmsk-12 からのパッチです。 ptmsk-13.patch

パッチで更新した箇所

  :
-VER=12
+VER=13
  :
;;; バージョン13 !

 
  :
-PARTS="gc dmsm bs hc dr lead lead2 br br2 bk bk_b bk_c kikako tyun"
+#PARTS="gc dmsm bs hc dr lead lead2 br br2 bk bk_b bk_c kikako tyun"
+PARTS="dmsm bs hc dr lead lead2 br br2 bk bk_b bk_c kikako tyun"
  : 
;;; ついにクリック音を外します


  :
 P=dmsm
 CH=3
 CH_VOL=50
-PROG=49		# string ensemble 1
+PROG=63		# SynthBrass 1
  :
;;; Cメロのストリングスの変更で
;;; 「ドミドミシミシミ」で使ってた音色を変わったしまった...
;;; デフォルトのストリングスを使ってるプログラム番号に変えて
;;; 「ドミドミシミシミ」の音色は従来のままに


  :
 P=lead2
 CH=6
 CH_VOL=50
-PROG=51		# synthstrings 1
+PROG=50		# String Ensemble 2
 PAN=44
  :
;;; 後半Aメロ(その3,4)で出てくるメロディの音色を変更
 

  :
 P=br
 CH=7
 CH_VOL=20
-PROG=62		# brass section
+PROG=62		# Brass Section : ptmsk_brass
 PAN=127
  :
;;; ブラスの音色のコメント箇所を修正
 

  :
 P=bk_c
 CH=11
 CH_VOL=40
-PROG=28		# electric guitar (clean)
+PROG=49		# String Ensemble 1 : ptmsk_strings
 PAN=84
  :
;;; Cメロのバッキングコードを重厚なストリングスにすべく作り直し

 
  :
 P=br2
 CH=2
 CH_VOL=20
-PROG=62		# brass section
+PROG=62		# Brass Section : ptmsk_brass
 PAN=0
  :
;;; ブラスのエコーの方も、音色のコメント箇所を修正
 

  :
--- ptmsk-12.tone	Tue Jun 23 23:00:00 2015
+++ ptmsk-13.tone	Wed Jun 24 00:00:00 2015
@@ -258,6 +258,49 @@
     gain 0
   chorus 1
 ,
+  name "ptmsk_strings"
+  vco
+    wave1 1
+    wave2 1
  :
;;; ストリングスの音を追加


  :
+  name "bass_drum_ptmsk"
+  vco
+    wave1 0
+    wave2 3
  :
;;; バスドラムの音を追加


  :
+  name "ptmsk_snare"
+  vco
+    wave1 2
+    wave2 3
  :
;;; スネアドラムの音を追加


  :
   note_name "36 Bass Drum 1"
   tone_compo
   {
-    name "bass drum"
+    name "bass_drum_ptmsk"
     note 28
     rate 1.000000
  :
;;; バスドラムの設定を追加した音に変更


  :
   note_name "40 Electric Snare"
   tone_compo
   {
-    name "snare"
+    name "ptmsk_snare"
     note 69
     rate 1.000000
  :
;;; スネアドラムの設定を追加した音に変更


  :
   note_name "-1 any"
   tone_compo
   {
-    name "strings"
+    name "ptmsk_strings"
     note -1
     rate 1.000000
  :
;;; ストリングスの設定を追加した音に変更

パッチをあててスクリプトと音色のファイルを更新し、実行してみます。

$ mv ptmsk-12.tone ptmsk-13.tone
$ mv ptmsk-12.sh ptmsk-13.sh
$ cat ptmsk-13.patch | patch -p0
$ ./ptmsk-13.sh

ptmsk-13.mp3


さらに修正

何度も聞いてると、Cメロのストリングスの出だしが大き過ぎが気になります。
ベロシティで徐々に音量を上げていくようにしてみます。

逆にAメロのバッキングの音量は小さ過ぎて、よく聞こえませんでした。
このあたりのバランスを少々変更。

さらにツールも修正。

まず、prog59の純正律関連から。

ドラムなどのチャンネルを純正律処理に関与させないために、次の条件で処理してました。
(note.c note_is_fix()関数)

これ以外にもピッチベンドの条件がありましたが、それはそれでいいとして...

これだけの条件では、パーカッション類の「チュチュン」や「キカコ」、 最初の段階のガイドクリックまで、コードのベースの判定に関与してしまってました。

チャンネル毎に純正律処理の有効/無効を指定できるように オプション -pure_enable=チャンネル指定 を追加してみます。

チャンネル指定は 調整用ボリューム から培ってきた例の指定方法で、 0なら純正律無効、1なら純正律有効、とします。

デフォルトは全部有効にしておいて、ドラム指定やノート固定の条件と「AND (かつ)」で効くようにしておきます。

ついでに tone.c の prog_name_tbl[] の定義。
末尾あたりにデータの抜けがあったので、修正を入れておきます。

続いて midtxt。

ここまで 4/4拍子 の曲前提できてますが、 例えば 3/4拍子 で問題ないか点検してみたところ「問題あり」です。

midtxt.c
  :
int
len_get_hl(char *buf, int div4)
{
	int h, l = 1;
	int ret = sscanf(buf, "%d/%d", &h, &l);
	if(ret == 0) ERR("format");
	return div4 * 4 * h / l;
}
  :

return の行で、 '* 4' してるのが、4/4拍子を前提にしてます。

このままでは 3/4拍子の曲を打ち込んだ場合、1小節分の時間が4/3倍と長くなって再生されてしまいます。
(まぁ、テンポの設定で逃げる事はできますが... ;-p)

一応、備えとして起動オプションで "-n4 1小節の四分音符の数" を指定出来るようにしておきます。
デフォルトの値は 4 とします。

今回のパッチファイルです。
ツール prog60_midtxt5.patch
スクリプト ptmsk-14.patch

更新の手順です。

$ cat prog60_midtxt5.patch | ( cd midi_prog ; make clean ; patch -p1 ; make)
;;; ツール

$ mv ptmsk-13.sh ptmsk-14.sh
$ cat ptmsk-14.patch | patch -p0
;;; スクリプト

$ mv ptmsk-13.tone ptmsk-14.tone
;;; 音色のデータ (そのまま)

$ ./ptmsk-14.sh
;;; スクリプトを実行

ptmsk-14.mp3


逆に波形データを読み込みMIDIデータを作らせてみる

photo musikに続いて、次なる打ち込み課題曲は...そうだ3拍子にしよう。
うんと古い曲で気に入ってるワルツの曲がありました。
喜多郎 「巡礼の旅 II」

でも実はCD持ってません。
小学生のときモノラルのラジカセで、FMで放送してたライブを録音したカセットテープの音源をずっと聞いてます。
高校生の頃に聴きすぎて、案の定カセットテープが伸びてしまいました。
長らく実家の戸棚にしまいこんでましたが、ある時期おそるおそる再生してMDに落とし...
さらにある時期にMP3ファイル落して、今だにひつこく良く聴いてます。

10分くらいの長さの素晴らしい曲ですが、状態はかなり悪いです。
どのくらい酷い音で聴いてるかと言うと

jrt2-sawari.mp3

まぁ我ながら、よくこの音質で聴きつづけてます。
では、耳コピー...
ってダメだ〜音がとれません。
テープが伸びてピッチも不安定ですが、本人の音感の才能の無さと、根気の無さ。

そんなこんなで、プログラムにこのボロボロの音質の波形データを聴かせて、 音符の情報を抽出できないものかと企んでます。

フーリエ変換の真似ごとをして、なんとかならないものだろうか? 色々試してみる事にします。

ふと気がつけば、最初の目標「MIDIデータから波形データを生成」から、
「波形データからMIDIデータを生成」へと、逆方向に走りだしてます。

という訳でここしばらく、ああでも無いこうでも無いと色々試してみてたのですが...
ようやく、何となくそれらしい音符の情報が抽出できはじめました。v^_^)

格闘してたソースコードはとっちらかってますので、 一旦整理してまとめなおしてから、新たな改善にチャレンジしてみます。


とりあえず部品の準備から

フーリエ変換の真似ごとに備えて、複素数のデータを貯めておくリングバッファもどきな部品から追加してみます。

波形データを読み込むプログラムの名前は「tool」としてみます。

まずは、ボコーダを作ったときの様に、生の波形データを読み込む部分だけを作っておきます。

tool1.patch

パッチをあててtoolをビルド
$ cat tool1.patch | (cd midi_prog ; make clean ; patch -p1 ; make tool)

生の波形データを用意
$ lame --decode jrt2-sawari.mp3 - | sox -t wav - -t raw jrt2-sawari.raw

とりあえず tool 実行
$ cat jrt2-sawari.raw | midi_prog/tool -c 1
v=0.337077
v=0.490829
v=0.455000
v=0.431501
v=0.474349
v=0.486465
v=0.460158
v=0.449629
v=0.470229
v=0.475845
v=0.437666
  :

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 | tail
v=-0.084933
v=-0.066500
v=-0.067934
v=-0.089145
v=-0.069033
v=-0.077090
v=-0.081973
v=-0.060671
v=-0.077853
v=-0.061007
$ 

未だ、読み込んだ生のデータを、ひたすら表示してるだけです。;-p)

未だtoolからは使ってませんが、リングバッファもどきのAPI概略です。


buf.h

rbuf構造体

struct rbuf{
	int n, cnt, is_cpx, n_bak;
	void *buf;
	double *d;
	double complex *c;
};

  n      : バッファはn個のデータを保持する。

  cnt    : 現在までに追加してきたデータの総数で、
           バッファ上には最後に追加されたn個しか存在しない。
           (と言いたいところですが...)
           バッファには初期状態からデータ0が満杯に詰ってる扱いとする。
           よってバッファにはいつもn個のデータが存在する。
           結局このcntは、データ追加用のAPIで追加してきたデータの総数という事になる。

  is_cpx : データとして複素数を扱う時のフラグ。

  n_bak  : 領域を割り付けるAPIで確保したデータ数nのバックアップ。
           (わけりにくい...)
           n_bak に対してnをより小さい値に変更して、
           一時的にバッファ全体の部分だけを使用する場合、
           アロケートしたバッファ全体を使うように戻す時に、
           nに、このn_bakの値を設定しなおす。

  buf    : データを保持するためにアロケートした領域。

  d      : 実数のポインタ。
           バッファで実数を扱う時に、bufの値をセットして使う。

  c      : 複素数のポインタ。
           バッファで複素数を扱う時に、bufの値をセットして使う。


領域確保

  void rbuf_init_alloc(struct rbuf *rb, int n, int is_complex);

    n個のデータ用の領域を割付ける。
    データの種別はis_complexが0なら実数、非0なら複素数。

  void rbuf_init_alloc_dbl(struct rbuf *rb, int n);

    n個の実数の領域を割付ける。

  void rbuf_init_alloc_cpx(struct rbuf *rb, int n);

    n個の複素数の領域を割付ける。


初期化

  void rbuf_init(struct rbuf *rb);

    バッファのcntを0にし、バッファの中身を全てデータ0にセットする。
    バッファのnを実際にアロケートされているn_bakの値に設定しなおす。

  void rbuf_init_n(struct rbuf *rb, int n);

    内部的にrbuf_init()を呼出て、バッファの中身をデータ0にセットしてから、
    バッファのnを、引数で指定した値にする。
    引数nの不正値判定はしない。

  void rbuf_copy_init(struct rbuf *rb, struct rbuf *src);

    引数srcが保持してるデータと同じ内容に初期化する。
    バッファのnの値は、ともに同じである前提。
    バッファのcntの値もsrcと同じ値にセットされる。


領域開放

  void rbuf_free(struct rbuf *rb);

    確保した領域を開放する。


配列としての設定/参照

  double rbuf_get_arr_dbl(struct rbuf *rb, int i);
  double complex rbuf_get_arr_cpx(struct rbuf *rb, int i);
  void rbuf_set_arr_dbl(struct rbuf *rb, int i, double v);
  void rbuf_set_arr_cpx(struct rbuf *rb, int i, double complex v);

    リングバッファとしてでは無く、
    配列としてデータを設定/参照する。
    set/get が 設定/参照。
    dbl/cpx が 実数用/複素数用。
    引数 i は 0 からバッファの n-1 まで指定可能。
    get はデータの値を返す。
    set は設定するデータを引数vで指定する。


リングバッファのデータ追加

  void rbuf_add_dbl(struct rbuf *rb, double v);
  void rbuf_add_cpx(struct rbuf *rb, double complex v);

    最古データの位置を、引数vのデータで上書きし、
    バッファのcntを1つ進める。


通し番号(serial index)管理用

  データ追加APIで追加してきたデータに対し、
  通し番号 0 から cnt-1 までが割り振られていると考える。

  int rbuf_oldest(struct rbuf *rb);

    バッファ上に存在する最古のデータの通し番号を返す。

  int rbuf_newest(struct rbuf *rb);

    バッファ上に存在する最新のデータの通し番号を返す。

  int rbuf_next(struct rbuf *rb);

    次にデータ追加APIでデータを追加した時に、
    割り振られる通し番号を返す。
    (つまり現在のバッファのcntを返すだけ)


通し番号による参照(直線補間あり)

  double rbuf_get_dbl(struct rbuf *rb, double sridx);
  double complex rbuf_get_cpx(struct rbuf *rb, double sridx);

    dbl/cpx が 実数用/複素数用。    
    引数 sridx は通し番号を実数で指定。
    指定した位置の前後の整数の通し番号のデータから、
    直線補間で求めた値を返す。
    指定の通し番号の位置のデータがバッファ上に存在するのか判定しない。
    呼出し側の責任で、存在する通し番号を指定すること。

まぁ、あんまりキッチリ書いても、ころころ変わるかも知れません...


そしてまだまだ部品の準備

フーリエ変換の真似ごとに向けて、さらなる部品の充実を計ります。

先のrbufに、サンプリング周波数の概念を追加して、 通し番号指定での参照の外に、時刻指定で参照出来るようにします。

rbufから継承して、tbufとします。

tbufからさらに継承して、sbufなるものを作ります。

sbufは複素数だけ扱うように固定です。
sbufの追加用の関数で指定された値の合計を保持しつつ、 その時点での合計の値を、内部のtbufに記録していきます。

これで、記録が残ってる分については、ある時点の合計が取得できます。
2点の合計を引き算すれば、ある時点からある時点までの合計が算出できるので、 積分に便利です。

この方式で実数で正の値のみを扱ったのならば、 保持してる合計が巨大に膨れあがり、 追加する値が相対的に小さくなって、 小数の足し算の精度の問題が思い浮かびます。

でも、元々-1.0〜1.0の範囲で変動する音声データを元に、 SIN,COSの複素数を掛け残した値を扱うので、 まぁ、まぁ、値の合計が偏って、膨れ上がったりはせんでしょう...;-p)

tbufから継承して、abufも用意しておきます。

こちらは実数専用で、sbufの積分結果の絶対値を、 もっとゆるい間隔で、貯めておく時に使います。

音符の情報として音量の変化を検出したい訳ですが、 結局MIDIのイベント時間の分解能より、 高い頻度で処理しても無駄なので、もっとゆるい間隔にします。

tool2.patch

$ cat tool2.patch | (cd midi_prog ; make clean ; patch -p1 ; make tool)

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 | head
v=0.000420
v=0.001032
v=0.001598
v=0.002133
v=0.002719
v=0.003317
v=0.003880
v=0.004426
v=0.004993
v=0.005562
$ 

とりあえずsbufバッファを使ってみてるだけです。

440Hzの半周期分の長さの8つ分の長さのsbufバッファを用意して...

読み込んだ生のデータと、
440HzのSIN波とCOS波(複素数)をかけ算して、
sbufバッファへ追加して...

sbufバッファに溜ってる値の平均値を取得して、
複素数の絶対値をとって表示してみてます。

追加したバッファのAPI概略です。

struct tbuf{
	struct rbuf rb;
	double smp_freq;
	double wsec;
};

  rb       : rbuf
  smp_freq : サンプリング周波数
  wsec     : バッファ長を時間(sec)に換算した値


初期化

  void tbuf_init(struct tbuf *tb, double smp_freq, double wsec, int is_complex);

    サンプリング周波数(smp_freq)と、バッファ長の時間(wsec)を指定する。


通し番号(sridx)と時間(sec)の変換

  /* sridx : serial index, rb->cnt scale */
  double tbuf_sec_to_sridx(struct tbuf *tb, double sec);
  double tbuf_sridx_to_sec(struct tbuf *tb, double sridx);


rbufの通し番号管理用(sridx)を時間(sec)に変換して返す

  double tbuf_oldest_sec(struct tbuf *tb);
  double tbuf_newest_sec(struct tbuf *tb);
  double tbuf_next_sec(struct tbuf *tb);


追加時間判定用

  int tbuf_is_over_sec(struct tbuf *tb, double sec);

    指定の時間(sec)が、次のデータ追加APIでデータを追加した時に、
    割り振られる時刻(tbuf_next_sec)を超過したか判定する。


データの追加

  void tbuf_add_dbl(struct tbuf *tb, double v);
  void tbuf_add_cpx(struct tbuf *tb, double complex v);


データの参照

  double tbuf_get_dbl(struct tbuf *tb, double sec);
  double complex tbuf_get_cpx(struct tbuf *tb, double sec);


struct sbuf{
	struct tbuf tb;
	double complex sum;
};

  sum : 追加APIで追加したデータの合計


初期化

  void sbuf_init(struct sbuf *sb, double smp_freq, double wsec);

    サンプリング周波数(smp_freq)と、バッファ長の時間(wsec)を指定する。


データの追加

  void sbuf_add(struct sbuf *sb, double complex v);

    v の合計を sum に追加しつつ、
    その時点での sum の値を、内部のtbufバッファに記録する。


指定範囲の合計

  double complex sbuf_sum(struct sbuf *sb, double from_sec, double to_sec);

    sbuf_addで追加したきたデータについて、指定範囲の合計を返す。
    (指定時刻は、バッファ上に残っているものでなければ、ダメ)
    

指定範囲の平均

  double complex sbuf_sum_mean(struct sbuf *sb, double from_sec, double to_sec);

    sbuf_sumをデータ数で割った値を返す


バッファ上の全範囲の平均

  double complex sbuf_sum_mean_all(struct sbuf *sb);



struct abuf{
	struct tbuf tb;
	double max_v;
	double sum;
};

  max_v : 追加APIで追加してきたデータの最大値
  sum   : バッファ上のデータの合計


初期化

  void abuf_init(struct abuf *ab, double smp_freq, double wsec);

    サンプリング周波数(smp_freq)と、バッファ長の時間(wsec)を指定する。


追加

  void abuf_add(struct abuf *ab, double v);


フーリエ変換もどきの方針

部品が揃いつつあるので、ここらで処理の方針などについて。

普通にフーリエ変換する場合、高速フーリエ変換(FFT)を含めて、 ある範囲のデータに区切って、その範囲のデータ列が周期的に繰り返されいている前提で、 周波数のスペクトルに変換すると思います。

例えば、サンプリング周波数SMP HzのN個のデータ列を変換すると、 一番高い周波数は、サンプリング定理からしてSMP/2 Hzです。

逆に一番低い周波数は、N個のデータのある区間が半周期分に相当する周波数。

N個の区間を時間に換算すると、1/SMP (sec) * N (個) で N/SMP (sec)
この時間が半周期分に相当するので、1周期分なら 2N/SMP (sec)
逆数とって周波数にすると SMP/(2N) (Hz) です。

そして、その間の周波数の分布については、一番低い周波数の次は、その2倍の周波数。
その次は3倍、その次は4倍、その次は5倍、.... ときて
(SMP/2) / (SMP/(2N)) 倍、つまりN倍で一番高い周波数 SMP/2 に至ります。

波形から抽出すべき周波数の範囲を考えてみます。

人の可聴範囲 20 Hzから 20000 Hz ではありますが、 MIDI的には、これまで作ってきたプログラムからして、 ノート番号0から127の範囲になります。

ノート番号69が、時報の低い「ポ」の音の440 Hzである事を手掛かりに、 あとは1オクターブ上がる毎に倍になって、 間は平均律という事で次の関係でした。

周波数 = 440 * pow(2, (ノート番号 - 69) / 12 )

ノート番号0の周波数
440 * e( (0-69)/12 * l(2) ) = 8.17579891564370733040

ノート番号127の周波数
440 * e( (127-69)/12 * l(2) ) = 12543.85395141597741023760

8 Hz ちょいから 12.5 kHz
20 Hz から 20 kHz に比べて低めですが、まぁそういうものなのでしょう。

仮に、抽出する一番低い周波数を 8 Hz に設定した場合を考えてみます。

サンプリング周波数 44100 Hz として、8 Hz の半周期分は、0.5/8 = 1/16 sec

ちなみに波形生成プログラム側のデフォルトのテンポの設定は4分音符120個/min。
なので、4分音符1つあたり1/2 sec。 1/16 sec は 32分音符分の長さに相当します。

抽出する周波数は下から順に 8 Hz, 16 Hz, 24 Hz, 32 Hz ときて...
44100 / 2 の 22050 Hz に至るまで、8 Hz刻みになるはずです。

この周波数の配置では、いかにも下側の低音で問題アリです。

まず、8Hz の次が1オクターブ上の16Hzって...
8 Hz から中ほどの 12 Hz くらいまでの幅の分は、8 Hz側に含まれて、 12 Hz くらいから 16 Hz までの幅の分は、16 Hz側に含まれてしまいます。

中ほどの440 Hz付近はどうかというと8Hz刻みなので 442 Hz, 440 Hz, 448 Hz になるはずで、

ノート番号 68, 69, 70の周波数

440 * e( (68-69)/12 * l(2) ) = 415.30469757994513852160
440 * e( (69-69)/12 * l(2) ) = 440.00000000000000000000
440 * e( (70-69)/12 * l(2) ) = 466.16376151808991640200

25 Hz間隔くらいです。
ということは、半音の幅を、3分割したくらいの精度でしょうか。

高い側をみてみると

ノート番号 125, 126, 127 の周波数

440 * e( (125-69)/12 * l(2) ) = 11175.30340585612430176040
440 * e( (126-69)/12 * l(2) ) = 11839.82152677230076537840
440 * e( (127-69)/12 * l(2) ) = 12543.85395141597741023760

11839-11175 = 664
12543-11839 = 704

650から700 Hzくらいの幅があります。

700/8 = 87.50000000000000000000

半音 700 Hzの幅を 8 Hz で刻んで 87分割!

音が高いので、そんなに押されないであろう鍵盤付近の周波数について、 そんなに高い精度で分割して調べてみても、 結局、結果は1つの鍵盤の情報に統合されてしまいます。

普通にフーリエ変換すると、真中あたりは良さそうですが、 低い側は精度が低いし、高い側は精度が高すぎてもったいない事になります。

ならばと、変換の対象区間を4倍くらいとったらどうか? 一番低い周波数が 2 Hz で、2 Hz刻みの分布になります。

8Hz 付近は 6 Hz, 8 Hz, 10 Hz まだまだ、足りません。

ノート番号127あたりは、87.5 * 4 = 350
半音を350分割で刻んで処理するので、パワーを大判振舞いし過ぎです。

そして区間が4倍ということで、32分音符分の4倍。 区間の長さは8分音符に相当します。

例えば、16分音符のON/OFFが連続してる箇所では、 2つ分まとめて変換されるので、かなり鈍ってしまいそうです。

結局、素のフーリエ変換結果の周波数分布は、音符の情報の抽出には向いてないようです。

そもそも、注目してる辺りの周波数のおよその強さを知りたいだけであって、 正確にフーリエ変換しても、逆変換して元に戻す事もありません。

44100 Hzのサンプリングで、1/16 sec分の区間をまじめに変換すると、 8 Hz から 8 Hz刻みで 22050 Hz まで。
中ほどは、程良い精度としても...

22050 / 8 - 1 = 2755.25

実に2700以上の周波数の情報に分解されてしまいます。
鍵盤の数は128個しかないというのに。

どうせなら、鍵盤の数128個に絞った周波数についてだけ、 計算したいところです。

だとしたら1つの鍵盤について、どのくらいの周波数の幅の変換になるだろうか?
ある鍵盤の1つ上の鍵盤の周波数は、常に pow(2, 1/12) 倍固定です。

周波数fの鍵盤について、どのくらいの区間を切り取って変換すれば、 1つ高い側の鍵盤の周波数が打ち消されて 0 になるか?

まずターゲットの周波数fに対して区間は半周期の整数倍でなければなりません。

区間を半周期のn倍として時間(sec)に直すには

周期 1/f sec の n/2倍 なので、n/(2f) sec

この区間(期間)に、1つ上の鍵盤の周波数の波が、半周期x(n+1)個分おさまっていれば、
区間内でターゲットとは半周期分ズレるので、打ち消されるはずです。

n/(2f) sec を (n+1)分割して2倍した時間が、1つ上の鍵盤の周期になるはずで、n/(f*(n+1)) sec

逆数とって周波数は f*(n+1)/n Hz

この周波数が 1つ上の鍵盤だから、f*pow(2, 1/12) Hz

f*(n+1)/n = f*pow(2, 1/12)

をnについて觧けばよくて

(n+1)/n = pow(2, 1/12)
n+1 = n*pow(2, 1/12)
1 = n*(pow(2, 1/12) - 1)

n = 1 / (pow(2, 1/12) - 1)
  = 1 / (e(1/12 * l(2)) - 1)
  = 16.81715374510576755683

n は整数だけど...
区間を長くとると周波数の幅は狭まり、
区間を短くとると周波数の幅は広がります。

周波数の幅が狭い方向に丸めると、
鍵盤と鍵盤の間をカバーしきれずにとりこぼすかも知れません。

ここは、クロスオーバー方向で、区間は短い側に切り捨てます。

ということで n = 16

ターゲットの周波数の半周期の16倍
つまり8周期分の区間をとれば、
だいたい1つ上の鍵盤にちょっと被るくらいの幅をカバーできるはず。


同様に1つ下側の鍵盤についても

f*(n-1)/n = f/pow(2, 1/12)
(n-1)/n = 1/pow(2, 1/12)
(n-1) = n/pow(2, 1/12)
n*(1-1/pow(2, 1/12) = 1
n = 1 / (1 - 1/pow(2, 1/12))
  = 1 / (1 - 1/e(1/12 * l(2)))
  = 17.81715374510576755605

下側は n = 17 で 8.5周期分で
だいたい1つ下の鍵盤にちょっと被るくらいの幅をカバーできるはず。

足りないより足りる側に合わせる方針ならば、 区間は短い側をとって8周期分になります。

この8周期分という結論は、どの位置の鍵盤についても言える事です。
となると、ターゲットの周波数ごとに、区間の長さは異る...
そう。
調べたい周波数ごとに、調べる波形の区間を変えれば、どの鍵盤の周波数についても、同程度の精度で算出できるはずです。


さらに部品 pitch 追加

という事で試してみる方針が定まったので、さらなる部品の追加です。

指定の周波数について、指定の幅(というか時間方向の長さ)で、 スペクトルの強さを調べるための部品 pitch を追加します。

tool3.patch

pitch の API概略

pitch.h

struct pitch{
	double freq;
	int wn;
	struct sbuf sb;
	struct abuf ab;
};

  freq : ターゲットの周波数
  wn   : freqの半周期分のwn倍の時間方向の長さのデータを処理する
  sb   : sbuf
  ab   : abuf


ユーテリティ

  double pitch_wn(double freq, double next_freq);
    freq      : ターゲットの周波数
    next_freq : ターゲットの周波数より高い周波数

    ターゲットfreqの半周期のwn倍の時間が、
    next_freqの半周期の(wn+1)倍の時間に相当するような
    実数wnを求めて返す

  double pitch_wsec(double freq, double wn);
    周波数freqの半周期のwn倍の時間を返す

  double pitch_freq(double wsec, double wn);
    wsecの時間に半周期がwn個納まる周波数を返す


初期化

  void pitch_init(struct pitch *p, double smp_freq, double freq, int wn,
		  double abuf_wsec);

    smp_freq : サンプリング周波数
    freq     : ターゲット周波数
    wn       : 処理するデータの時間方向の長さ指定
               freqの半周期分のwn倍の時間指定になる
    abuf_wsec: abufのバッファ長を時間(sec)で指定する


追加

  void pitch_add(struct pitch *p, double v);

    保持してるターゲット周波数から、
    複素数の搬送波(?)を算出してかけ算し、
    sbufに追加

    abufのサンプリング周波数は、2*96 Hzに固定
    (4分音符が2Hzのテンポで、4分音符の分解能が96分割)

    abufへの追加時刻になれば、
    sbufの積分結果の絶対値を求めて abuf に追加する
$ cat tool3.patch | (cd midi_prog ; make clean ; patch -p1 ; make tool)

$ cat jrt2-sawari.raw | midi_prog/tool -c 1
sec=-0.005208 v=0.000000
sec=0.000000 v=0.005661
sec=0.005208 v=0.018591
sec=0.010417 v=0.020496
sec=0.015625 v=0.031438
sec=0.020833 v=0.048862
sec=0.026042 v=0.069820
sec=0.031250 v=0.073963
sec=0.036458 v=0.085415
  :
sec=59.947917 v=0.891867
sec=59.953125 v=0.896591
sec=59.958333 v=0.902171
sec=59.963542 v=0.911136
sec=59.968750 v=0.917551
sec=59.973958 v=0.920630
sec=59.979167 v=0.930051
sec=59.984375 v=0.941333
sec=59.989583 v=0.949535
$ 

tool.c

#include "util.h"
#include "in.h"
#include "pitch.h"

int
main(int ac, char **av)
{
	in_rec_t in;
	double v;

	struct pitch p;
	double freq = 440.0;
	double next_freq = 440 * pow(2, (70-69)/12.0);
	int wn = (int) pitch_wn(freq, next_freq);
	double abuf_wsec = opt_double("-abuf_wsec", ac, av, 0.5);
	double sec, sec_bak = -1;

	in_init(&in, ac, av);
	pitch_init(&p, in.smp_freq, freq, wn, abuf_wsec);

	while(in_do_mono(&in, &v) != EOF){
		pitch_add(&p, v);
		sec = tbuf_newest_sec(&p.ab.tb);
		if(sec <= sec_bak) continue;
		printf("sec=%f v=%f\n", sec, p.ab.sum);
		sec_bak = sec;
	}
	return 0;
}

追加した部品 pitch のお試し動作確認です。

ターゲット freq 440 Hz で、 1つ上のノート番号の周波数までの幅を指定し、 abufの長さはデフォルト0.5秒で pitch を初期化。

pitchにデータを追加していって、 pitch内部のabufへのデータ追加があれば、 abuf上に存在する値の合計を、時間とともに表示してます。

部品 pitch のデータ追加処理について

void
pitch_add(struct pitch *p, double v)
{
	struct tbuf *stb = &p->sb.tb;
	double sec = tbuf_next_sec(stb);
	double complex arg = I * 2 * M_PI * p->freq * sec;

	sbuf_add(&p->sb, v * cexp(-arg));

	if(tbuf_is_over_sec(&p->ab.tb, sec - stb->wsec * 0.5)){
		abuf_add(&p->ab, cabs( sbuf_sum_mean_all(&p->sb) ) );
	}
}


そしてラスボス的な部品 pitdet 追加

複数の「部品 pitch」を束ねて管理する、ラスボス的な「部品 pitdet (pitch detect)」を追加します。

tool4.patch

pitdet API概略

pitdet.h

struct pitdet{
	int div;
	int m;
	struct pitch *arr;
	double oldest_sec, newest_sec;
};

  div        : 1つのノート番号あたりの分割数
  m          : 保持するpitchの数 (128 * div)
  arr        : pitchの配列
  oldest_sec : 各pitchの最古データの時刻の中で、最も新しい時刻
  newest_sec : 各pitchの最新データの時刻の中で、最も古い時刻

  (どのpitchでもデータが存在する範囲は、oldest_sec から newest_sec までになる)


ユーテリティ

  double pitdet_note_to_freq(double note);

    note番号(実数)から周波数への変換

  double pitdet_freq_to_note(double freq);

    周波数からnote番号(実数)への変換


初期化

  void pitdet_init(struct pitdet *pd, double smp_freq,
		   int div, double abuf_wsec);

    smp_freq : サンプリング周波数
    div      : 1つのノート番号あたりの分割数
    abuf_wsec: 各pitchのabufのバッファ長を時間(sec)で指定する
    

追加

  void pitdet_add(struct pitdet *pd, double v);

    保持してるpitch群の追加用のAPI pitch_add を呼び出す

tool.c の処理について

#include "util.h"
#include "in.h"
#include "pitdet.h"

int
main(int ac, char **av)
{
	in_rec_t in;
	double v;

	struct pitdet pd;
	int div = opt_int("-div", ac, av, 1);
	double abuf_wsec = opt_double("-abuf_wsec", ac, av, 0.5);
	double old_bak = 0, new_bak = 0;

	in_init(&in, ac, av);
	pitdet_init(&pd, in.smp_freq, div, abuf_wsec);

	while(in_do_mono(&in, &v) != EOF){
		pitdet_add(&pd, v);
		if( pd.oldest_sec > 0 && pd.newest_sec > 0 &&
		    (pd.oldest_sec > old_bak || pd.newest_sec > new_bak) ){
			printf("%f %f\n", pd.oldest_sec, pd.newest_sec);
			old_bak = pd.oldest_sec;
			new_bak = pd.newest_sec;
		}
	}
	return 0;
}

なので、pitdetのほんのお試し処理で、音符抽出としては未だあまり意味のない処理です。

$ cat tool4.patch | (cd midi_prog ; make clean ; patch -p1 ; make tool)

$ cat jrt2-sawari.raw | midi_prog/tool -c 1
0.005208 0.005208
0.010417 0.005208
0.010417 0.010417
0.015625 0.010417
0.015625 0.015625
0.020833 0.015625
0.020833 0.020833
0.026042 0.020833
0.026042 0.026042
0.031250 0.026042
0.031250 0.031250
0.036458 0.031250
0.036458 0.036458
0.041667 0.036458
0.041667 0.041667
  :
4.411458 4.406250
4.411458 4.411458
4.416667 4.411458
4.416667 4.416667
4.421875 4.416667
4.421875 4.421875
4.427083 4.421875
4.427083 4.427083
  :

oldest から newest まで、ほとんど幅が無い...

abuf_wsec 0.5 秒では、足りてないようです。

ノート番号1つあたりの分割数 div 1 で試しているので、

pitchのsbufのバッファ長を指定する、半周期分の波が納まる個数 wnは 16

ノート番号0について

周波数は 440 * e( (0-69)/12 * l(2) ) = 8.17579891564370733040
逆数とって周期は 1 / 8.17579891564370733040 = 0.12231220585508575176 sec

sbufのバッファ長は 0.12231220585508575176 / 2 * 16 = 0.97849764684068601408 sec

1秒弱

sbufの真中のデータの追加時刻が、abufへ積分結果を追加する時刻になるので、
abufの最新のデータの時刻は、subfに最新のデータを追加した時刻から
0.5秒弱の遅れです。


ノート番号69について

周波数は 440
周期は 1 / 440 sec

sbufのバッファ長 1 / 440 / 2 * 16 = 0.01818181818181818176 sec

abuf最新時刻は、sbuf最新時刻から
1 / 440 / 2 * 16 / 2 = 0.00909090909090909088 sec の遅れ


ノート番号127について

の周波数は 440 * e( (127-69)/12 * l(2) ) = 12543.85395141597741023760
周期は 1 / 12543.85395141597741023760 = 0.00007972031593106342 sec

sbufのバッファ長 0.00007972031593106342 / 2 * 16 = 0.00063776252744850736 sec

abuf最新時刻は、sbuf最新時刻から

0.00063776252744850736 / 2 = 0.00031888126372425368 sec の遅れ


ノート番号0の遅れが0.5秒なので、
abufのバッファ長0.5秒を、ほとんど使ってしまってます。


  sbuf                         最新
				|
		      abuf      |
  note127          |---------|  |
			    最新|
		abuf            |
  note0      |---------| 0.5sec |
		      最新
		   |   |
		   |   |
	       --->|   |<--- ここが狭い!


という事で、全てのpitchのabufで共通して有効な時間の幅が狭いです

abuf_wsec の指定としてデフォルトの0.5 secから倍の1 secを指定してみると

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -abuf_wsec 1
0.005208 0.500000
0.005208 0.505208
0.010417 0.505208
0.010417 0.510417
0.015625 0.510417
0.015625 0.515625
0.020833 0.515625
0.020833 0.520833
0.026042 0.520833
0.026042 0.526042
0.031250 0.526042
0.031250 0.531250
0.036458 0.531250
0.036458 0.536458
  :
4.197917 4.697917
4.203125 4.697917
4.203125 4.703125
4.208333 4.703125
4.208333 4.708333
4.213542 4.708333
4.213542 4.713542
4.218750 4.713542
4.218750 4.718750
4.223958 4.718750
  :

0.5秒程度の範囲になりました。

ノート番号1つあたりの分割数を、デフォルトの1から4にしてみると

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -div 4
0.005208 0.005208
0.010417 0.005208
0.015625 0.005208
0.020833 0.005208
0.031250 0.005208
0.036458 0.005208
0.041667 0.005208
0.046875 0.005208
  :
1.770833 0.135417
1.776042 0.135417
1.776042 0.140625
1.781250 0.140625
1.781250 0.145833
1.786458 0.145833
  :

範囲のfromとtoが逆転して、おかしな事に...

分割数divを増やすと、1つのpitchあたりの周波数の幅は狭まります。

という事はpitchのsbufのバッファ長は増えて、abufの時刻の遅れは増えます。

abufのバッファ長が、デフォルトの0.5秒の設定では、 全てのabufで共通して有効範囲が存在しない状態になってしまいました。

div 4倍で、周波数の幅 1/4で、sbufのバッファ長 4倍で、abufの遅れの最大 4倍とするならば、
0.5秒弱の遅れが、2秒弱の遅れ...
テンポ120/分の4/4拍子なら1小節分の遅れ...!

まぁそれでも abuf_wsec として 2秒のバッファを確保すれば、なんとか有効範囲が存在する?

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -div 4 -abuf_wsec 2
0.005208 0.010417
0.005208 0.015625
0.010417 0.015625
0.010417 0.020833
0.015625 0.020833
0.015625 0.026042
0.020833 0.005208
0.026042 0.005208
0.026042 0.010417
0.031250 0.010417
0.031250 0.015625
0.036458 0.015625
0.036458 0.020833
0.041667 0.020833
  :
0.364583 0.223958
0.364583 0.229167
0.369792 0.229167
0.369792 0.234375
0.375000 0.234375
  :
3.312500 3.177083
3.317708 3.177083
3.317708 3.182292
3.322917 3.182292
3.322917 3.187500
  :

ダメです。若干足りてません。

リングバッファなので最大遅れの人とは逆に、遅れが少ない人はどんどんデータを上書きして進んでいくので、 そっち側からも範囲が狭まってきます。

2.5秒くらいとればどうか?

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -div 4 -abuf_wsec 2.5
0.005208 0.364583
0.005208 0.369792
0.010417 0.369792
0.010417 0.375000
0.015625 0.375000
0.015625 0.380208
0.020833 0.380208
0.020833 0.385417
0.026042 0.385417
0.026042 0.390625
0.031250 0.390625
0.031250 0.395833
0.036458 0.395833
0.036458 0.401042
0.041667 0.401042
  :
4.000000 4.364583
4.005208 4.364583
4.005208 4.369792
4.010417 4.369792
4.010417 4.375000
4.015625 4.375000
4.015625 4.380208
4.020833 4.380208
4.020833 4.385417
4.026042 4.385417
4.026042 4.390625
  :

有効範囲 0.36 sec 程の幅でOKです。


スペクトルを表示してみる

とりあえず pitdet でフーリエ変換もどきをかけてみて、結果のスペクトルを表示してみます。

tool5.patch

pitdet の構造体を少々変更してます。

 	int div;
 	int m;
 	struct pitch *arr;
-	double oldest_sec, newest_sec;
+
+	struct{
+		double sec;
+		int update;
+	} oldest, newest;
+
+	double max_v;
 };
 

スペクトル表示部分は、とりあえずtool.c側に入れてます。

$ cat tool5.patch | (cd midi_prog ; make clean ; patch -p1 ; make tool)

$ cat jrt2-sawari.raw | midi_prog/tool -c 1
sec=0.000000 00000000000000000000000000000000000000000000023332100010012221100001000001110000000000000000000000000000000000000000000000111110
sec=0.005208 00000000000000000000000000000000000000000000023432101100013331001011000001110010000000000000000000000000000000000000000000000000
sec=0.010417 00000000000000000000000000000000000000000001013442011000003431000011100000000110000000000000000000000000000000000000000000000000
sec=0.015625 00000000000000000000000000000000000000000001014542011010003531110121000110100000000000000000000000000000000000000000000000000000
sec=0.020833 00000000000000000000000000000000000000000000014541010000103530110111001100100000000000000000000000000000000000000000000000000000
sec=0.026042 00000000000000000000000000000000000000000000104641110010103531000111001100010000000000000000000000000000000000000000000000000000
sec=0.031250 00000000000000000000000000000000000000000000104640110100003420110232000100000000000000000000000000000000000000000000000000000000
sec=0.036458 00000000000000000000000000000000000000000000104641100010103430100111000100000000000000000000000000000000000000000000000000000000
sec=0.041667 00000000000000000000000000000000000000000000104640100000003430110120000100000000000000000000000000000000000000000000000000000000
sec=0.046875 00000000000000000000000000000000000000000000104640010100003420000110000110000000000000000010000000000000000000000000000000000000
sec=0.052083 00000000000000000000000000000000000000000000104641110010102431000111100100000000000000000000000000000000000000000000000000000000
sec=0.057292 00000000000000000000000000000000000000000000104540110000003420110121000010000000000000000000000000000000000000000000000000000000
sec=0.062500 00000000000000000000000000000000000000000000104530000000002320100111000000000000000000000000000000000000000000000000000000000000
sec=0.067708 00000000000000000000000000000000000000000000104530100000102320000121001000000000000000000000000000000000000000000000000000000000
sec=0.072917 00000000000000000000000000000000000000000000103530110100002310110232000000000000000000000000000000000000000000000000000000000000
sec=0.078125 00000000000000000000000000000000000000000000103530100000102320100011000000110000000000000000000000000000000000000000000000000000
sec=0.083333 00000000000000000000000000000000000000000000103530100000002320010121000000000000000000000000000000000000000000000000000000000000
sec=0.088542 00000000000000000000000000000000000000000000103530010000002310000121000000000000000000000000000000000000000000000000000000000000
sec=0.093750 00000000000000000000000000000000000000000000103530100000001320000111000000100000000000000000000000000000000000000000000000000000
sec=0.098958 00000000000000000000000000000000000000000000103430000000002210010121000000000000000000000000000000000000000000000000000000000000
sec=0.104167 00000000000000000000000000000000000000000000003430000000001210000111000000000000000000000000000000000000000000000000000000000000
sec=0.109375 00000000000000000000000000000000000000000000003430100000001211000011000000100000000000000000000000000000000000000000000000000000
sec=0.114583 00000000000000000000000000000000000000000000003430000000001110000121000000000000000000000000000000000000000000000000000000000000
sec=0.119792 00000000000000000000000000000000000000000000003430100000000110000010000000100000000000000000000000000000000000000000000000000000
sec=0.125000 00000000000000000000000000000000000000000000103420000000000110000011000000000000000000000000000000000000000000000000000000000000
sec=0.130208 00000000000000000000000000000000000000000000103420000000000100000010000000000000000000000000000000000000000000000000000000000000
sec=0.135417 00000000000000000000000000000000000000000000002420000000000010000011000000100000000000000000000000000000000000000000000000000000
sec=0.140625 00000000000000000000000000000000000000000000002420000000000000000010000000000000000000000000000000000000000000000000000000000000
sec=0.145833 00000000000000000000000000000000000000000000002320000000000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.151042 00000000000000000000000000000000000000000000002320000000000000000010000000000000000000000000000000000000000000000000000000000000
sec=0.156250 00000000000000000000000000000000000000000000002320000000000000000010000000000000000000000000000000000000000000000000000000000000
sec=0.161458 00000000000000000000000000000000000000000000002320000000000000000000000000110000000000000000000000000000000000000000000000000000
sec=0.166667 00000000000000000000000000000000000000000000002320000000000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.171875 00000000000000000000000000000000000000000000002320000000000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.177083 00000000000000000000000000000000000000000000002320000000000000000000000000100000000000000000000000000000000000000000000000000000
sec=0.182292 00000000000000000000000000000000000000000000002320000000000000000000000000000000000000000000000000000000000000000000000000000000

  :

sec=0.468750 00000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.473958 00000000000000000000000000000000000000000000000100000000000000000000000000100000000000000000000000000000000000000000000000000000
sec=0.479167 00000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.484375 00000000000000000000000000000000000000000000000110000000000000000000000000100000000000000000000000000000000000000000000000000000
sec=0.489583 00000000000000000000000000000000000000000000010010000000000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.494792 00000000000000000000000000000000000000000000011011111111000000000000000000000000000000000000000000000000000000000000000000000000
sec=0.500000 00000000000000000000000000000000000000000000001022222121111221100000000000000000000000000000000000000000000000000000000000000000
sec=0.505208 00000000000000000000000000000000000000000000001023332211100121111111112111100000000000000000000000000000000000000000000000000000
sec=0.510417 00000000000000000000000000000000000000000000100123433210111232221000111001100000000000000000000000000000000000000000000000000000
sec=0.515625 00000000000000000000000000000000000000000000110114543201112113321000221012210010000110010000000000000000000000000000000000000000
sec=0.520833 00000000000000000000000000000000000000000000010024553111101212320000231002210110000000000000000000000000000000000000000000000000
sec=0.526042 00000000000000000000000000000000000000000000010024663011012102430110233000110000000000000000000000000000000000000000000000000000
sec=0.531250 00000000000000000000000000000000000000000000001025762010100002331110022011110010000000000000100000000000000000000000000000000000
sec=0.536458 00000000000000000000000000000000000000000000000115751100000101331000122200110000000000000000000000000000000000000000000000000000
sec=0.541667 00000000000000000000000000000000000000000000000016851110110212431010121111101110000000000000000000000000000000000000000000000000
sec=0.546875 00000000000000000000000000000000000000000000010116851110000013310111331000100000000000000000000000000000000000000000000000000000
sec=0.552083 00000000000000000000000000000000000000000000010116850210100123311000110011100000011000000011000000000000000000000000000000000000
sec=0.557292 00000000000000000000000000000000000000000000010116740200010002320001221001100100000000000000000000000000000000000000000000000000
sec=0.562500 00000000000000000000000000000000000000000000000116740210100213320001220010110000000000000000000000000000000000000000000000000000
sec=0.567708 00000000000000000000000000000000000000000000000216740110000003310110221000100000000000000000000000000000000000000000000000000000
sec=0.572917 00000000000000000000000000000000000000000000000205740110100112320100121001100111000000000000000000000000000000000000000000000000
sec=0.578125 00000000000000000000000000000000000000000000010105740100010002320000121001100010000000000000000000000000000000000000000000000000
sec=0.583333 00000000000000000000000000000000000000000000000105740110100112321110121000110011000000000000000000000000000000000000000000000000
sec=0.588542 00000000000000000000000000000000000000000000010115640010000102310000121000110000000000000000000000000000000000000000000000000000
sec=0.593750 00000000000000000000000000000000000000000000000114640110100112320000111001100110000000000000000000000000000000000000000000000000
sec=0.598958 00000000000000000000000000000000000000000000000105640100000002320000121001100110000000000000000000000000000000000000000000000000
sec=0.604167 00000000000000000000000000000000000000000000000104640110100112310100121000110000000000000000000000000000000000000000000000000000
sec=0.609375 00000000000000000000000000000000000000000000000204630110000102310110111001100000000000000000000000000000000000000000000000000000
sec=0.614583 00000000000000000000000000000000000000000000000104630100100111320000111000100000000000000000000000000000000000000000000000000000
sec=0.619792 00000000000000000000000000000000000000000000000104530100000001220000110001100110000000000000000000000000000000000000000000000000
sec=0.625000 00000000000000000000000000000000000000000000000004530110000012210000121000100000000000000000000000000000000000000000000000000000
sec=0.630208 00000000000000000000000000000000000000000000000004530110000002210000010001100000000000000000000000000000000000000000000000000000
sec=0.635417 00000000000000000000000000000000000000000000000004530100000101320000011000110000000000000000000000000000000000000000000000000000
sec=0.640625 00000000000000000000000000000000000000000000000103530100000001221000010001100000000000000000000000000000000000000000000000000000
sec=0.645833 00000000000000000000000000000000000000000000000103530000000002210010111000110000000000000000000000000000000000000000000000000000
sec=0.651042 00000000000000000000000000000000000000000000000103530110000001210100000000100000000000000000000000000000000000000000000000000000
sec=0.656250 00000000000000000000000000000000000000000000000103530100000101210000011000110000000000000000000000000000000000000000000000000000
sec=0.661458 00000000000000000000000000000000000000000000000103430100000001210000010001100000000000000000000000000000000000000000000000000000
sec=0.666667 00000000000000000000000000000000000000000000000103420000000001210000110000100000000000000000000000000000000000000000000000000000
sec=0.671875 00000000000000000000000000000000000000000000000003430100000001210000000000000000000000000000000000000000000000000000000000000000
sec=0.677083 00000000000000000000000000000000000000000000000003420000000101210000011000000000000000000000000000000000000000000000000000000000

  :

なかなか無理のある表示...

その時点までの全てのabufの中の最大値を基準にして、 128個の鍵盤について、0から最大値までを'0'から'9'の1文字として表示してます。

そしてさらに、もし負の値なら'-'、最大値以上ならば'+'にしてます。

sec=0.552083 00000000000000000000000000000000000000000000010116850210100123311000110011100000011000000011000000000000000000000000000000000000

この辺りで、比較的大きな'8' が出ててます。
これが基音だとすれば...

12個右の1オクターブ上あたりにも頂上が'3'の山があって、 倍音成分が影のように現れる模様。

さらに7個右は3倍音あたりで、わずかに'1'と...

倍音はとりあえず置いといて、基音に注目。

... 0010116850210100 ...

ピークはあるものの、ノート番号3つ分が強く現れてます。

そして、8, 5, 0 と下がってから 2, 1, 0 と下がりなおし。

これはスペクトルの強さが、sync関数の絶対値で分布してるという事でしょうか?

半音を4分割してみて、画面幅130で表示してみます。

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -div 4 -show_w 130
  :
sec=4.520833 1100 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.526042 0100 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.531250 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.536458 1000 0011 1111 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.541667 1000 0011 1111 1111 0100 0001 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.546875 1000 0011 1111 1111 1111 0101 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.552083 0100 0001 1122 2221 1111 1101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 1100 00
sec=4.557292 0100 0001 1222 2222 2111 1000 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 1100 00
sec=4.562500 0000 0001 1223 3322 2111 0000 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 1100 00
sec=4.567708 1000 0000 1223 3332 2110 0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 1100 00
sec=4.572917 1000 0000 1233 3333 2100 0001 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 1100 00
sec=4.578125 1000 0000 1233 4433 2100 0001 0000 0000 0000 0000 0000 0000 0000 0000 0110 0000 0000 0000 1000 0000 0000 0000 1100 00
sec=4.583333 0000 0000 1234 4443 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0111 0100 0000 0000 0000 0000 0000 0000 1100 00
sec=4.588542 0000 0000 0234 4443 1000 1000 1100 0000 0000 0000 0000 0000 0000 0000 0111 1110 0000 0000 0000 0000 0000 0001 1100 00
sec=4.593750 0000 0000 0234 5542 1001 0000 1000 0000 0000 0000 0000 0000 0000 0000 0111 1110 0000 0000 0000 0000 0000 0001 1000 00
sec=4.598958 1000 0000 0135 5542 1011 0000 0000 0000 0000 0000 0000 0000 0000 0000 0011 1110 0000 0000 0000 0000 0000 0001 1000 00
sec=4.604167 1000 0000 0135 5542 0011 0000 0000 0000 0000 0000 0000 0000 0000 0000 0011 1100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.609375 0000 0000 0135 6542 0110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0012 2100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.614583 0000 0001 0135 6541 0110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0012 2100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.619792 0000 0001 0135 6631 1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0012 2100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.625000 0000 0000 0035 7631 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0012 2100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.630208 0000 0000 1036 7631 1100 1000 0000 0000 0000 0000 0000 0000 0000 0000 0012 2100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.635417 0000 0000 1036 7631 2101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0001 1000 00
sec=4.640625 0000 0000 1026 8631 2101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0001 1100 00
sec=4.645833 0000 0000 1026 8621 2101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0001 1100 00
sec=4.651042 0000 0000 1026 8621 2011 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 1100 00
sec=4.656250 0000 0000 1126 8622 2010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 1100 00
sec=4.661458 0000 0000 1126 9622 2010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 1100 00
sec=4.666667 0000 0000 0116 9612 1010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 1100 00
sec=4.671875 0000 0000 0116 9612 1010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 1000 00
sec=4.677083 0000 0000 0116 9612 1100 1000 0000 0000 0000 0000 0000 0000 0000 0000 0002 2100 0000 0000 0000 0000 0000 0000 1000 00
sec=4.682292 0000 0000 0116 +612 1100 1000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 1000 00
sec=4.687500 0000 0000 0216 9602 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 1000 00
sec=4.692708 0000 0000 0216 9602 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 1000 00
sec=4.697917 0000 0001 0216 9602 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.703125 0000 0001 0216 9602 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 1100 00
sec=4.708333 0000 0001 0215 9601 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 1100 00
sec=4.713542 0000 0001 0215 8601 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 1100 00
sec=4.718750 0000 0001 0215 8501 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 0000 00
sec=4.723958 0000 0001 0215 8501 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 0000 00
sec=4.729167 0000 0001 0215 8501 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 0000 00
sec=4.734375 0000 0001 0204 8511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 0000 00
sec=4.739583 0000 0001 0104 7511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.744792 0000 0000 0104 7511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.750000 1000 0000 0104 7511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.755208 0000 0000 0104 7511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.760417 0000 0000 0104 7511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.765625 0000 0000 0104 7511 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.770833 0000 0000 0104 7401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.776042 0000 0000 0104 6401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.781250 0000 0000 0104 6401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.786458 0000 0000 0103 6401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.791667 0000 0000 0103 6401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.796875 0000 0000 0103 6401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.802083 0000 0000 0103 6401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.807292 0000 0000 0103 5401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.812500 0000 0000 0103 5401 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0001 1100 0000 0000 0000 0000 0000 0000 0000 00
sec=4.817708 0000 0000 0103 5401 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.822917 0000 0000 0103 5301 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.828125 0000 0000 0103 5301 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.833333 0000 0000 0103 5301 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.838542 0000 0000 0103 5300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.843750 0000 0000 0103 5300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.848958 0000 0000 0103 4300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.854167 0000 0000 0103 4301 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.859375 0000 0000 0103 4301 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.864583 0000 0000 0103 4201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.869792 0000 0000 0103 4201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.875000 0000 0000 0003 4201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.880208 0000 0000 0003 4201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.885417 0000 0000 0003 4201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.890625 0000 0000 0003 4201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.895833 0000 0000 0003 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.901042 0000 0000 0003 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.906250 0000 0000 0002 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.911458 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.916667 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.921875 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.927083 0000 0000 0002 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.932292 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.937500 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.942708 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.947917 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.953125 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.958333 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.963542 0000 0000 0102 3100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.968750 0000 0000 0102 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.973958 0000 0000 0102 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.979167 0000 0000 0102 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.984375 0000 0000 0102 3201 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.989583 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=4.994792 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.000000 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.005208 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.010417 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.015625 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.020833 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.026042 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.031250 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.036458 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.041667 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.046875 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.052083 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.057292 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.062500 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.067708 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.072917 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.078125 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.083333 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.088542 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.093750 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.098958 0000 0000 0002 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.104167 0000 0000 0001 3200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.109375 0000 0000 0001 2200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.114583 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.119792 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.125000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.130208 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.135417 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.140625 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.145833 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.151042 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
sec=5.156250 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
  :

これで時刻4秒半から5秒ちょいくらいまでです。

sec=4.682292 0000 0000 0116 +612 1100 1000 0000 ...

ここで最大値 '+' が出てます。
中央がノート番号64なので、ノート番号が、56だか57だけの辺り。

この精度の表示では、いまいちですが、ピーク右側はそれっぽく。

そしてそれとは別に、ピークの位置がノート番号の境界に近い...

pitdet.c みなおしてみると

double
pitdet_note_to_freq(double note)
{
	return 440 * pow(2, (note - 69) / 12);
}

double
pitdet_freq_to_note(double freq)
{
	return log(freq / 440) / log(2) * 12 + 69;
}

void
pitdet_init(struct pitdet *pd, double smp_freq,
	    int div, double abuf_wsec)
{
	int j;

	pd->div = div;
	pd->m = pd->div * 128;
	if((pd->arr = malloc(sizeof(*pd->arr) * pd->m)) == NULL) ERR("No Mem");

	for(j=0; j<pd->m; j++){
		double note = (double)j / pd->div - 0.5;
		double next_note = (double)(j+1) / pd->div - 0.5;
		double freq = pitdet_note_to_freq(note);
		double next_freq = pitdet_note_to_freq(next_note);
		int wn = (int) pitch_wn(freq, next_freq);

		pitch_init(&pd->arr[j], smp_freq, freq, wn, abuf_wsec);
	}
  :

適当に - 0.5 してる処理が、まずかった

例えば

div=1 ならば

j       :  0    1    2
j / div :  0    1    2
note    : -0.5  0.5  1.5


div=2 ならば

j       :  0    1    | 2   3    | 4
j / div :  0    0.5  | 1   1.5  | 2
note    : -0.5  0    | 0.5 1    | 1.5


div=3 ならば

j       :  0    1    2    | 3   4    5
j / div :  0    0.33 0.67 | 1   1.33 1.67
note    : -0.5 -0.17 0.17 | 0.5 0.83 1.17


div=4 ならば

j       :  0    1     2    3    | 4
j / div :  0    0.25  0.5  0.75 | 1
note    : -0.5 -0.25  0    0.25 | 0.5

中央のあたりがノート番号の真中になるよう、良かれと思って -0.5 してたのですが、狙いがズレてました。

  :
	for(j=0; j<pd->m; j++){
                double note = (j + 0.5) / pd->div - 0.5;
                double next_note = (j + 1 + 0.5) / pd->div - 0.5;
  :

とすれば

div=1

j       :  0    1    2
j / div :  0    1    2
note    :  0    1    2


div=2

j       :  0    1    | 2    3    | 4
j / div :  0    0.5  | 1    1.5  | 2
note    : -0.25 0.25 | 0.75 1.25 | 1.75


div=3

j       :  0     1    2    | 3    4    5
j / div :  0     0.33 0.67 | 1    1.33 1.67
note    : -0.33  0    0.33 | 0.67 1    1.33


div=4

j       :  0      1     2     3     | 4
j / div :  0      0.25  0.5   0.75  | 1
note    : -0.375 -0.125 0.125 0.375 | 0.625

こうすべきかと。
次回のパッチで反映しておきます。


スペクトルのピークを強調してみる

ピークを強調というよりも、ピークの位置の周辺部分に出てる「影」を削る処理を追加してみます。

ある位置のpitch構造体に割り当ててる周波数は、半波長wn個分の長さでフーリエ変換的な処理をしてます。
そのwnという整数は、1つ高い方のpitch構造体の周波数で、半波長のwn+1個分の長さをとったときに、 だいたい一致するような整数wnとして決めてました。
ぴったり割り切れないので、カバーする帯域がクロスする側に切り捨てて、wnを決めてます。

例えば、あるpitch構造体の周波数にドンピシャな正弦波が鳴っていたとしても、 1つ高い側、1つ低い側のpitch構造体の処理では、wn+1, wn-1 と微妙にズレているので、 影のようにスペクトルが現れてくるはずです。

この「影」の影響が大きいと、1つの鍵盤を弾いているのか、2つの隣あった鍵盤を弾いているのか判定しにくいはず。

この影が「だーいたいsync関数」の分布で出て来ると仮定して、取り除く処理を追加してみます。

tool6.patch

まず最初に、半音をdiv分割したときの周波数の割振りがまずかった箇所を修正。

 	for(j=0; j<pd->m; j++){
-		double note = (double)j / pd->div - 0.5;
-		double next_note = (double)(j+1) / pd->div - 0.5;
-		double freq = pitdet_note_to_freq(note);
-		double next_freq = pitdet_note_to_freq(next_note);
+		double note = (j + 0.5) / pd->div - 0.5;
+		double next_note = (j + 1 + 0.5) / pd->div - 0.5;
+		double freq = pitdet_note_to_freq(pd, note);
+		double next_freq = pitdet_note_to_freq(pd, next_note);
 		int wn = (int) pitch_wn(freq, next_freq);

次にピッチのチューニングを入れておきます。

先の-div 4のスペクトル表示の結果の箇所

  :
sec=4.682292 0000 0000 0116 +612 1100 1000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 1000 00
  :

どうも、想定してる一般的なチューニングから半音の半分くらいズレてる様子です。

ライブ演奏を収録した放送だったので、当時のステージでのアナログシンセのチューニングの問題。
何十年も前にモノラルのラジカセで録音した時のモーターの回転数。
繰返し聴いた時のテープの伸び。
永年保管した後、MDに落すために再生したときのカセット再生機のモータの回転数の違い。

まぁ、どれだけズレてても不思議はありません。

ノート番号69が440Hzとしてきましたが、半音を100分割したcentの単位で調整できるようにしてみました。
起動時に '-adj_cent <値>' で指定。省略時のデフォルト値は0です。

例えば '-adj_cent 0' では、ノート番号69 が 440 Hz, ノート番号70 が 466.16376151808991640200 Hz 。
'-adj_cent 50' に指定すると、ノート番号69 を間の453 Hzとして扱います。

  :
 double
-pitdet_note_to_freq(double note)
+pitdet_note_to_freq(struct pitdet *pd, double note)
 {
-	return 440 * pow(2, (note - 69) / 12);
+	return 440 * pow(2, (note - 69 + pd->adj_cent * 0.01) / 12);
 }
 
 double
-pitdet_freq_to_note(double freq)
+pitdet_freq_to_note(struct pitdet *pd, double freq)
 {
-	return log(freq / 440) / log(2) * 12 + 69;
+	return log(freq / 440) / log(2) * 12 + 69 - pd->adj_cent * 0.01;
 }
 
 void
-pitdet_init(struct pitdet *pd, double smp_freq,
+pitdet_init(struct pitdet *pd, double smp_freq, int adj_cent,
 	    int div, double abuf_wsec)
 {
 	int j;
 
 	pd->div = div;
+	pd->adj_cent = adj_cent;
 	pd->m = pd->div * 128;
  : 
 struct pitdet{
 	int div;
+	int adj_cent;
 	int m;
 	struct pitch *arr;
  :
-void pitdet_init(struct pitdet *pd, double smp_freq,
+void pitdet_init(struct pitdet *pd, double smp_freq, int adj_cent,
 		 int div, double abuf_wsec);
  :
 	struct pitdet pd;
+	int adj_cent = opt_int("-adj_cent", ac, av, 0);
 	int div = opt_int("-div", ac, av, 1);
 	double abuf_wsec = opt_double("-abuf_wsec", ac, av, 2.5);
 
 	in_init(&in, ac, av);
-	pitdet_init(&pd, in.smp_freq, div, abuf_wsec);
+	pitdet_init(&pd, in.smp_freq, adj_cent, div, abuf_wsec);
  : 

そして「影」を取り去る処理。
取り去る前と比べてみたいので、コマンドライン引数で -test_off を指定したときは、処理しないようにしてみます。

--- midi_prog-/tool.c	2015-08-14 00:00:00.000000000 +0900
+++ midi_prog/tool.c	2015-08-22 00:00:00.000000000 +0900
@@ -55,6 +55,51 @@
 	int j, js, je;
 	char s[32];
 	double sec = pd->oldest.sec;
+	int test_off = opt_idx("-test_off", ac, av) > 0;
+	struct{
+		int stat;
+		double v;
+	} *wk;
+
+	if((wk = malloc(sizeof(*wk) * pd->m)) == NULL) ERR("No Mem");
+
+	for(j=0; j<pd->m; j++){
+		struct pitch *p = &pd->arr[j];
+		wk[j].stat = 0;
+		wk[j].v = tbuf_get_dbl(&p->ab.tb, sec);
+	}
+
+	while(!test_off){
+		int t = -1, j;
+		double v_max = 0;
+		for(j=0; j<pd->m; j++){
+			if(wk[j].stat != 0) continue;
+			if(t < 0 || wk[j].v > v_max){
+				t = j;
+				v_max = wk[j].v;
+			}
+		}
+		if(t < 0) break;
+		wk[t].stat++;
+		
+		for(j=0; j<pd->m; j++){
+			double a, x, k;
+
+			if(wk[j].stat != 0) continue;
+
+			/*			
+			wn/(2*freq) = (wn+a)/(2*freq[j])
+			a = wn*freq[j]/freq - wn
+			  = wn*(freq[j]/freq - 1)
+			*/
+			a = pd->arr[t].wn * (pd->arr[j].freq / pd->arr[t].freq - 1);
+			x = a * M_PI;
+			k = sin(x) / x;
+			k = k < 0 ? -k : k;
+
+			wk[j].v -= wk[t].v * k;
+		}
+	}
 
 	sprintf(s, "sec=%f ", sec);
 	printf("%s", s);
  :

とりあえず tool.c に追加して、毎回 malloc(), free() してます。
(覚えておいて、pitdet側に取り込むときになんとかしよう...)

まずデフォルトの div=1 で試してみます。

$ cat tool6.patch | (cd midi_prog ; make clean ; patch -p1 ; make tool)

前回のある時刻の結果
sec=0.552083 00000000000000000000000000000000000000000000010116850210100123311000110011100000011000000011000000000000000000000000000000000000

周波数割当ての変更分だけ反映してみると
$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -test_off | grep 'sec=sec=0.552083'
sec=0.552083 00000000000000000000000000000000000000000000010038721201100123201001210111000000011000000111000000000000000000000000000000000000

確かにちょっとズレてます。

そして「影」の除去処理をON
$ cat jrt2-sawari.raw | midi_prog/tool -c 1 | grep 'sec=sec=0.552083'
sec=0.552083 0000000000000000000000000000000000000000000000--18720100000013200000100010000000010000000010000000000000000000000000000000000000

ピークの両サイドの直近は、低い側が多く削られている?
そして、直近よりも離れた側がゴッソリいかれてる様子。
削り過ぎて負の値になってる箇所に'-'が出てます。

直近の低い側の方がクロスオーバーしてる分が大きかったはずなので、まぁそうなるでしょう。
そして、離れるほどピッタリ半周期の整数倍からもズレるので、まぁそうなるでしょう。

次に、チューニングを +50 cent で扱ってみた場合

影除去なし
$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -test_off -adj_cent 50 | grep 'sec=sec=0.552083'
sec=0.552083 00000000000000000000000000000000000000000000101168502101001233110001100111000000110000000110000000000000000000000000000000000000

影除去あり
$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 | grep 'sec=sec=0.552083'
sec=0.552083 0000000000000000000000000000000000000000000000--58401000000023000001100010000000010000000000000000000000000000000000000000000000

... 0--1872010 ...
から
... 0--5840100 ...
に

8,7 と同じくらいのピークの紛らわしい感じが、
5,8,4 になって、pitch構造体で設定して狙ってる周波数に一致してきてる感触です。

次に分割数4の場合

前回のある時刻の結果
sec=4.682292 0000 0000 0116 +612 1100 1000 0000 0000 0000 0000 0000 0000 0000 0000 0001 2100 0000 0000 0000 0000 0000 0000 1000 00

周波数割当ての変更分だけ反映してみると
$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -test_off -div 4 -show_w 130 | grep 'sec=4.682292'
sec=4.682292 0000 0000 1139 9322 0101 0000 1000 0000 0000 0000 0000 0000 0000 0000 0012 2000 0000 0000 0000 0000 0000 0000 1000 00

ピーク 6,'+',6 から 3,9,9,3 に分配が変って

さらにチューニングを50 centつけてみると

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -test_off -adj_cent 50 -div 4 -show_w 130 | grep 'sec=4.682292'
sec=4.682292 0000 0011 3993 2201 0100 0010 0000 0000 0000 0000 0000 0000 0000 0000 1220 0000 0000 0000 0000 0000 0000 0010 0000 00

3,9,9,3 が想定してる鍵盤のど真ん中にのってきた感じです

そして「影」除去をON

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 4 -show_w 130 | grep 'sec=4.682292'
sec=4.682292 0000 0000 2993 1200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0220 0000 0000 0000 0000 0000 0000 0010 0000 00

0,0,1,1 , 3,9,9,3 , 2,2,0,1
から
0,0,0,0 , 2,9,9,3 , 1,2,0,0
に

直近の隣と上側がの削りが弱目ですが、狙った効果は出てる様子です


とりあえずMIDIデータを出してみる

スペクトルの表示を眺めつつ、なんとなく良さげな感じが得られているので、 とりあえずこの状態から、MIDIデータを吐かせて試してみます。

まず、「影」を除去する処理をtool.cからpitdet.[ch]へ移しておきます。

tool7.patch

--- midi_prog-/pitdet.h	2015-08-22 00:00:00.000000000 +0900
+++ midi_prog/pitdet.h	2015-08-25 00:00:00.000000000 +0900
@@ -15,14 +15,24 @@
 	} oldest, newest;
 
 	double max_v;
+	double thres;
+
+	struct{
+		int stat;
+		double v;
+	} *wk;
 };
 
 double pitdet_note_to_freq(struct pitdet *pd, double note);
 double pitdet_freq_to_note(struct pitdet *pd, double freq);
 
 void pitdet_init(struct pitdet *pd, double smp_freq, int adj_cent,
-		 int div, double abuf_wsec);
+		 int div, double abuf_wsec, double thres);
 
 void pitdet_add(struct pitdet *pd, double v);
 
+int pitdet_sec_to_tick(struct pitdet *pd, double sec);
+int pitdet_onoff_judge(struct pitdet *pd, double v);
+int pitdet_onoff(struct pitdet *pd, double sec, int j, double *ret_v);
+
 #endif

--- midi_prog-/pitdet.c	2015-08-22 00:00:00.000000000 +0900
+++ midi_prog/pitdet.c	2015-08-25 00:00:00.000000000 +0900
@@ -14,7 +14,7 @@
 
 void
 pitdet_init(struct pitdet *pd, double smp_freq, int adj_cent,
-	    int div, double abuf_wsec)
+	    int div, double abuf_wsec, double thres)
 {
 	int j;
 
@@ -39,6 +39,61 @@
 	pd->newest.update = 0;
 
 	pd->max_v = -1;
+	pd->thres = thres;
+
+	if((pd->wk = malloc(sizeof(*pd->wk) * pd->m)) == NULL) ERR("No Mem");
+}
+
+static void
+shadow_cut(struct pitdet *pd, double sec)
+{
+	int j;
+
+	for(j=0; j<pd->m; j++){
+		double v;
+		int onoff = pitdet_onoff(pd, sec, j, &v);
+		pd->wk[j].stat = onoff ? 0 : -1;
+		if(onoff) pd->wk[j].v = v;
+	}
+
+	while(1){
+		int t = -1;
+		double v_max = 0;
+		for(j=0; j<pd->m; j++){
+			if(pd->wk[j].stat != 0) continue;
+			if(t < 0 || pd->wk[j].v > v_max){
+				t = j;
+				v_max = pd->wk[j].v;
+			}
+		}
+		if(t < 0) break;
+		pd->wk[t].stat++;
+
+		for(j=0; j<pd->m; j++){
+			double a, x, k;
+
+			if(pd->wk[j].stat != 0) continue;
+
+			/*			
+			wn/(2*freq) = (wn+a)/(2*freq[j])
+			a = wn*freq[j]/freq - wn
+			  = wn*(freq[j]/freq - 1)
+			*/
+			a = pd->arr[t].wn * (pd->arr[j].freq / pd->arr[t].freq - 1);
+			x = a * M_PI;
+			k = sin(x) / x;
+			k = ABS(k);
+
+			pd->wk[j].v -= pd->wk[t].v * k;
+			if(!pitdet_onoff_judge(pd, pd->wk[j].v)) pd->wk[j].stat = -1;
+		}
+	}
+
+	for(j=0; j<pd->m; j++){
+		struct pitch *p = &pd->arr[j];
+		double v = pd->wk[j].stat >= 0 ? pd->wk[j].v : -1;
+		tbuf_set_dbl(&p->ab.tb, sec, v);
+	}
 }
 
 void
@@ -63,4 +118,27 @@
 	}
 	if((pd->oldest.update = (pd->oldest.sec != osec)) != 0) pd->oldest.sec = osec;
 	if((pd->newest.update = (pd->newest.sec != nsec)) != 0) pd->newest.sec = nsec;
+
+	if(pd->oldest.update && pd->oldest.sec >= 0) shadow_cut(pd, pd->oldest.sec);
+}

  :

+int
+pitdet_onoff_judge(struct pitdet *pd, double v)
+{
+	return v / pd->max_v >= pd->thres;
+}
+
+int
+pitdet_onoff(struct pitdet *pd, double sec, int j, double *ret_v)
+{
+	double v = tbuf_get_dbl(&pd->arr[j].ab.tb, sec);
+	if(ret_v) *ret_v = v;
+	return pitdet_onoff_judge(pd, v);
 }


tool.c
  :
+	double thres = opt_double("-thres", ac, av, 0.1);
  :

tool.c の「影」除去処理は、pitdet.c のshadown_cut()へと移動してまとめ、 pitdet_add()で波形データを解析するときに呼出すようにしておきます。

その際パラメータ thres を追加してます。
鍵盤ON/OFFを判定する閾値のパラメータで、使用する時点までに算出してきたスペクトルの最大値に対する割合を指定します。
pitdet_onoff_judge()でパラメータthres を参照し、指定したスペクトルの値についてON/OFFを判定します。
pitdet_onoff()では、指定のpitchの指定時刻のスペクトルの値を取得して、pitdet_onoff_judge()でON/OFFを判定して返します。
そして、shadow_cut()の「影」除去処理では、pitdet_onoff()を使い、OFF判定の領域は出来るだけ不要な処理をサボって最適化してます。

tool.cのmain()からの処理では、スペクトル表示は"-show"オプションの指定があるときだけにして、 "-midi"オプションの指定があると、MIDIデータ(の元となるテキスト)を出力するように変更してます。

tool.c

  :

+	int show = opt_idx("-show", ac, av) > 0;
+	int midi = opt_idx("-midi", ac, av) > 0;
  :
+		if(show) show_spectre(&pd, ac, av);
+		if(midi) midi_out(&pd);
  :

+static void
+midi_out(struct pitdet *pd)
+{
+	double sec = pd->oldest.sec;
+	int i, k;
+	int tick, ch = 0, velo = 100;
+
+	for(i=0; i<NOTE_N; i++){
+		int onoff = 0;
+		for(k=0; k<pd->div; k++){
+			int j = i * pd->div + k;
+			onoff |= pitdet_onoff(pd, sec, j, NULL);
+		}
+		if(!chatt_add(&chatt[i], sec, onoff)) continue;
+
+		tick = pitdet_sec_to_tick(pd, chatt[i].sec);
+		printf("abs=%08d %s ch=%d note=%d velo=%d\n",
+		       tick, chatt[i].onoff ? "on " : "off", ch, i, velo);
+		fflush(stdout);
+	}
 }
 

この tool.c midi_out()関数が要の処理です。

秒の時刻からイベントのtickへの変換処理をpitdet.cに追加してます。

pitdet.c

  :

+int
+pitdet_sec_to_tick(struct pitdet *pd, double sec)
+{
+	struct tbuf *tb = &pd->arr[0].ab.tb;
+	return (int) tbuf_sec_to_sridx(tb, sec);
+}
+

元々 pitch の abuf は、音声波形のサンプリング周波数で処理しても、 どうせイベントtickでしかMIDIデータは出力できないとして、 サンプリング周波数をゆるく落してました。

pitch の abuf は2 Hzの四分音符を96分割して 2 * 96 で 200 Hz弱のサンプリング周波数に設定してます。
なので、pitdet からは pitch の保持する abufの、継承元の親の tbuf の機能を使って、秒からtickに変換してます。

そしてチャタリング除去。

tool.c

  :

+struct chatt{
+	double lmt_sec, sec, chg_try_sec;
+	int onoff;
+} chatt[ NOTE_N ];
+
+void
+chatt_init(struct chatt *ct, double lmt_sec)
+{
+	ct->lmt_sec = lmt_sec;
+	ct->sec = 0;
+	ct->chg_try_sec = -1;
+	ct->onoff = 0;
+}
+
+int
+chatt_add(struct chatt *ct, double sec, int onoff)
+{
+	if(onoff == ct->onoff){
+		ct->chg_try_sec = -1;
+		return 0;
+	}
+	if(sec - ct->sec < ct->lmt_sec) return 0;
+
+	if(ct->chg_try_sec < 0){
+		ct->chg_try_sec = sec;
+		return 0;
+	}
+	if(sec - ct->chg_try_sec < ct->lmt_sec) return 0;	
+
+	ct->sec = ct->chg_try_sec;
+	ct->onoff = onoff;
+	ct->chg_try_sec = -1;
+	return 1;
+}

  :

+	double lmt_sec = opt_double("-lmt_sec", ac, av, 2.0/32);

チャタ除去なしでON/OFF判定結果をそのまま使ってみると、 ON/OFFのはざま辺りのスペクトルの強さの時刻で、 1 tick刻みでON/OFFのイベントが量産されてしまってました。

200 Hz程度の速さで鍵盤のON/OFF。
高橋名人でも無理でしょう。

という事でバタつきの除去を導入します。
某SHなんとかのガラケーの案件で、キーのチャタ除去処理をどうしてたか思い出しつつ...
パラメータ lmt_sec で指定した期間、状態が変化しなければ、遡ってその状態になった時刻を採用するという方式にしてます。
lmt_sec のデフォルト設定は、2/32秒にしてます。
1小節2秒くらいと考えて、32分音符の長さ以下の変化を受付けないようにしてるつもりです。

そりでは、試してみます。

$ cat tool7.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -midi

しばしカウントダウン的に標準エラー出力に表示が出て

abs=00000012 on  ch=0 note=45 velo=100
abs=00000012 on  ch=0 note=46 velo=100
abs=00000012 on  ch=0 note=47 velo=100
abs=00000012 on  ch=0 note=58 velo=100
abs=00000012 on  ch=0 note=65 velo=100
abs=00000025 off ch=0 note=58 velo=100
abs=00000031 off ch=0 note=65 velo=100
abs=00000078 off ch=0 note=45 velo=100
abs=00000093 off ch=0 note=46 velo=100
abs=00000095 on  ch=0 note=48 velo=100
abs=00000095 on  ch=0 note=49 velo=100
abs=00000095 on  ch=0 note=50 velo=100
abs=00000097 off ch=0 note=47 velo=100
abs=00000097 on  ch=0 note=61 velo=100
abs=00000097 on  ch=0 note=68 velo=100
abs=00000105 on  ch=0 note=60 velo=100
abs=00000106 on  ch=0 note=62 velo=100
abs=00000141 off ch=0 note=60 velo=100
abs=00000144 off ch=0 note=68 velo=100
abs=00000147 off ch=0 note=62 velo=100
abs=00000151 off ch=0 note=61 velo=100
abs=00000163 off ch=0 note=48 velo=100
abs=00000200 off ch=0 note=50 velo=100
abs=00000200 on  ch=0 note=52 velo=100
abs=00000200 on  ch=0 note=53 velo=100
  :

こんな感じでMIDIのノートON/OFFイベントの元になるテキストを、
標準出力に出します。

注意として、abs=xxx の時刻の並びは前後します。
チャタ除去で遡って時刻を決定しているためです。
この出力結果をさらにソートせねばなりません。

ここまできたら、さらにヘッダを付けてバイナリのMIDIデータに変換して、さらに波形を生成してみて比較したいところ...

という事で、スクリプトを作ってみました。

jrt2-midi.sh

#! /bin/bash

SONG=jrt2

mkdir -p tmp_$SONG
cd tmp_$SONG

RAW=../jrt2-sawari.raw
if [ $# -ge 1 ] ; then
  RAW=../$1
fi

DIV=5
ADJ_CENT=50

VOL=all:2.5
CH=0
CH_VOL=100
PROG=24		# SIN wave

if [ ! -e midi_prog ] ; then
  cp -r ../midi_prog/ .
  if [ ! -e cui ] ; then
    cp -r ../cui/ .
  fi
  (cd midi_prog ; make clean ; make)
fi

MDIR=midi_prog
MIDTXT=$MDIR/midtxt
MIDPROG=$MDIR/prog60
TOOL=$MDIR/tool

#
# Header
#
cat > hdr <<EOF
MThd
hdsz=6
fmt_type=0
track_num=1
div4=96
MTrk
trksz=-1
EOF

#
# Setting
#
cat > setting <<EOF
delta=0 prog_num ch=$CH v=$PROG
delta=0 ctl_chg ch_vol_msb ch=$CH v=$CH_VOL
EOF

#
# Pickup
#
echo Pickup
if [ ! -e abs_ ] ; then
  cat $RAW | $TOOL -c 1 -adj_cent $ADJ_CENT -div $DIV -midi -thres 0.1 > abs_
  cp abs_ abs
else
  cat abs_ | head -n $(wc -l abs_) > abs
fi

#
# MIDI out
#
echo MIDI out
sort abs | $MIDTXT -tx_delta > note
cat hdr setting note > $SONG.txt
cat $SONG.txt | $MIDTXT -r > $SONG.mid

#
# Wave out
#
echo Wave out
rm -f data
TONE=../tone.txt
if [ -e $TONE ] ; then
	(echo load ; cat $TONE) > data
fi
cat $SONG.mid >> data

PROG_OPT="-nocui -c 1 -adj_cent $ADJ_CENT -vol $VOL"
cat data | $MIDPROG $PROG_OPT > $SONG.raw
if which sox ; then
  cat mix.raw | sox -t raw -r 44100 -c 1 -b 16 -s - $SONG.wav
  if which lame ; then
    lame $SONG.wav $SONG.mp3
  fi
fi

#
# ;-p
#
echo Mix ;-p
RAWMIX2=$MDIR/rawmix2

cat $RAW | $RAWMIX2 -r $SONG.raw > mix.raw

if which sox ; then
  cat mix.raw | sox -t raw -r 44100 -c 2 -b 16 -s - mix.wav
  if which lame ; then
    lame mix.wav mix.mp3
  fi
fi

#
# ...
#
echo Div 3M

echo Div 3M
COUNT=30
dd if=mix.raw of=mix_0.raw bs=1M count=$COUNT
dd if=mix.raw of=mix_1.raw bs=1M count=$COUNT skip=$COUNT
dd if=mix.raw of=mix_2.raw bs=1M count=$COUNT skip=$(expr $COUNT '*' 2)

if which sox ; then
  cat mix_0.raw | sox -t raw -r 44100 -c 2 -b 16 -s - mix_0.wav
  cat mix_1.raw | sox -t raw -r 44100 -c 2 -b 16 -s - mix_1.wav
  cat mix_2.raw | sox -t raw -r 44100 -c 2 -b 16 -s - mix_2.wav
  if which lame ; then
    lame mix_0.wav mix_0.mp3
    lame mix_1.wav mix_1.mp3
    lame mix_2.wav mix_2.mp3
  fi
fi

# EOF

ターゲットの演奏波形データのチューニングを、50 centズラして扱っているので、 MIDIデータから波形を生成する prog60 側も同様に、チューニング対応しておきます。

--- midi_prog-/note.c	2015-06-25 00:00:00.000000000 +0900
+++ midi_prog/note.c	2015-08-25 00:00:00.000000000 +0900
@@ -6,6 +6,7 @@
 static int note_pure = 0, note_base = -1, note_pure_cent = 25;
 static int pure_enable[MIDI_CH_N];
 static double note_base_freq;
+static int adj_cent = 0;
 
 static struct{
 	int h, l;
@@ -30,6 +31,8 @@
 	int ch, v;
 	char *s;
 
+	adj_cent = opt_int("-adj_cent", ac, av, 0);
+
 	note_pure = opt_idx("-pure", ac, av) >= 0;
 	note_pure_cent = opt_int("-pure_cent", ac, av, note_pure_cent);
 
@@ -102,13 +105,13 @@
 static double
 note_to_freq(int note)
 {
-	return 440 * pow(2, (note - 69) / 12.0);
+	return 440 * pow(2, (note - 69 + adj_cent *0.01) / 12.0);
 }
 
 static double
 freq_to_note(double freq)
 {
-	return (log(freq / 440) / log(2)) * 12 + 69;
+	return (log(freq / 440) / log(2)) * 12 + 69 - adj_cent * 0.01;
 }
 
 static int

また、prog60で生成する波形の音色は、とりあえず一発目なので倍音成分も含めてノート番号に落ちてるであろうことから、 単純なSIN波形に設定してます。

音色のプログラム番号は24を使い、24に設定している音色データ tone.txt を次の手順で用意します。

SIN波形の音色データ用 tone.patch

$ echo save | midi_prog/prog60 > tone.txt
$ cat tone.patch | patch -p0

そしておまけ。
生成したMIDIデータから波形データを生成し、オリジナルの演奏と比較してみます。

これだけのために、rawmix2 という簡単なツールをでっちあげてみました。

--- midi_prog-/rawmix2.c	1970-01-01 09:00:00.000000000 +0900
+++ midi_prog/rawmix2.c	2015-08-25 00:00:00.000000000 +0900
@@ -0,0 +1,37 @@
+#include "util.h"
+#include "in.h"
+#include "out.h"
+
+int
+main(int ac, char **av)
+{
+	FILE *fp_l = fp_get("-l", ac, av, "-", "r");
+	FILE *fp_r = fp_get("-r", ac, av, "-", "r");
+	FILE *fp_o = fp_get("-o", ac, av, "-", "w");
+	struct out_rec otr;
+	in_rec_t in_l, in_r;
+
+	if(fp_l == NULL || fp_r == NULL || fp_o == NULL) ERR("Can't open");
+
+	out_init(&otr, ac, av);
+	otr.fp = fp_o;
+
+	in_init(&in_l, ac, av);
+	in_l.fp = fp_l;
+	in_l.ch_num = 1;
+
+	in_init(&in_r, ac, av);
+	in_r.fp = fp_r;
+	in_r.ch_num = 1;
+
+	while(1){
+		double v[2];
+		if(in_do(&in_l, &v[0]) == EOF) break;
+		if(in_do(&in_r, &v[1]) == EOF) break;
+		out_do2(&otr, v);
+	}
+
+	fclose(fp_l);
+	fclose(fp_r);
+	return 0;
+}

ボコーダのところで作ったfp_get()関数を使いたいので、 util.[ch] に移動し、voco.c からもutil.c使うように変更してます。

では、jrt2-midi.sh の実行。

tool では-div 5として、1つのノート番号を5分割して試してるので、 かなり処理時間がかかります。

$ ./jrt2-midi.sh

まず、tmp_jrt2/ 以下に midi_prog/ などをcopyしてビルドし、そちらのバイナリを使うようにしてます。
結果の生成物も全て tmp_jrt2/ 以下に出力します。

jrt2-midi.sh スクリプトの引数に .raw ファイル(44100 Hz, 16 bit, signed, モノラル) を指定すると、
ターゲットの入力ファイるを変更出来ます。
  :

ということで、jrt2-mono.raw を指定して、延々と時間をかけて生成した音声ファイルです。
mix_0.mp3
mix_1.mp3
mix_2.mp3

10分くらいの長さなので、サイトの容量制限の都合で3つに分割してます。

左チャンネルがオリジナルの演奏で、右チャンネルが抽出した音符データから波形を生成しなおしたものです。

曲の構成として大きく3つの楽章(?)に別れてますが、1つめの楽章はかなり再現できてて、いい感じ。
対して、2つめ3つめの楽章はまだまだ改善の余地ありです。


「影」除去処理の修正

「影」除去処理を見直してみると、間違い発見!
修正しておきます。

pitdet.c

  :
 			wn/(2*freq) = (wn+a)/(2*freq[j])
 			a = wn*freq[j]/freq - wn
 			  = wn*(freq[j]/freq - 1)
+
+			fix.
+
+			(wn[j]+a)/(2*freq) = wn[j]/(2*freq[j])
+			a = wn[j]*freq/freq[j] - wn[j]
+                          = wn[j]*(freq/freq[j] - 1)
 			*/
-			a = pd->arr[t].wn * (pd->arr[j].freq / pd->arr[t].freq - 1);
+			a = pd->arr[j].wn * (pd->arr[t].freq / pd->arr[j].freq - 1);
 			x = a * M_PI;
 			k = sin(x) / x;
 			k = ABS(k);
  :

注目しているピークの位置から見て、除去される側の位置の偏角を求めてましたが、 正しくは、除去される側からみての、ピーク位置の偏角を求めねばなりません。

高い側の削りが甘くなっていたのは、そのせいだったかも...

ついでにスクリプトファイルも少々修正。

tool8.patch
jrt2-midi.sh-1.patch

$ cat tool8.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-1.patch | patch -p0

まず div=1

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -show
sec=0.000000 --------------------------------------------13321-------1221--------------------------------------------------------------------
sec=0.005208 --------------------------------------------1342--------1232-------------1------------------------------------------------------
sec=0.010417 --------------------------------------------1342---------243------1-------------------------------------------------------------
sec=0.015625 --------------------------------------------1353---------252----11--------------------------------------------------------------
sec=0.020833 --------------------------------------------1353---------252--------------------------------------------------------------------
sec=0.026042 ---------------------------------------------463-------1-252-----1--------------------------------------------------------------
sec=0.031250 ---------------------------------------------463---------341----121-------------------------------------------------------------
sec=0.036458 -------------------------------------------1-463---------242--------------------------------------------------------------------
sec=0.041667 -------------------------------------------1-462---------242----11--------------------------------------------------------------
sec=0.046875 -------------------------------------------1-363---------341-----1--------------------------------------------------------------
sec=0.052083 ---------------------------------------------363---------142-----11-------------------------------------------------------------
sec=0.057292 ---------------------------------------------352---------241-----21-------------------------------------------------------------
sec=0.062500 ---------------------------------------------352---------231-----1--------------------------------------------------------------
sec=0.067708 -------------------------------------------1-352---------231-----11-------------------------------------------------------------
sec=0.072917 ---------------------------------------------352---------23-----121-------------------------------------------------------------
sec=0.078125 ---------------------------------------------352---------131-----11-------------------------------------------------------------
sec=0.083333 -------------------------------------------1-352---------131-----11-------------------------------------------------------------
sec=0.088542 ---------------------------------------------352---------23------1--------------------------------------------------------------
sec=0.093750 ---------------------------------------------352---------122-----11-------------------------------------------------------------
sec=0.098958 ---------------------------------------------342---------121-----1--------------------------------------------------------------
sec=0.104167 ---------------------------------------------342---------121-----1--------------------------------------------------------------
sec=0.109375 ---------------------------------------------342----------11-----1--------------------------------------------------------------
sec=0.114583 ---------------------------------------------242---------11-----121-------------------------------------------------------------
sec=0.119792 ---------------------------------------------242----------1--------------1------------------------------------------------------
sec=0.125000 ---------------------------------------------241----------1------1--------------------------------------------------------------
sec=0.130208 ---------------------------------------------242-----------------1--------------------------------------------------------------
sec=0.135417 ---------------------------------------------242-----------------1--------------------------------------------------------------
  :

なんか、見事に負の値にまで削れてる様子

前回比較してた時刻については

前回の「影」の除去OFFの結果
sec=0.552083 00000000000000000000000000000000000000000000101168502101001233110001100111000000110000000110000000000000000000000000000000000000

前回の「影」の除去ONの結果
sec=0.552083 0000000000000000000000000000000000000000000000--58401000000023000001100010000000010000000000000000000000000000000000000000000000

今回の結果 (影除去は常にON)
sec=0.552083 ------------------------------------------------583--------132-----11-----------------------------------------------------------

見事に削れてます。...
が、なぜか1オクターブ上に見えてる倍音のピークがズレ?


そして div=4 の場合

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 4 -show -show_w 130
sec=0.000000 ---- ---- ---- ---1 2332 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.005208 ---- ---- ---- ---1 2332 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.010417 ---- ---- ---- ---1 2332 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.015625 ---- ---- ---- ---1 2332 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.020833 ---- ---- ---- ---1 2332 ---- ---- ---- ---- ---- ---- 1111 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.026042 ---- ---- ---- ---- 2432 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.031250 ---- ---- ---- ---- 2432 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- --1- ---- ---- ---- ---- ---- ---- --
sec=0.036458 ---- ---- ---- ---- 2431 ---- ---- ---- ---- ---- ---- -22- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- --
sec=0.041667 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -22- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- --
sec=0.046875 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -22- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.052083 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -22- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.057292 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -22- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.062500 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -221 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.067708 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -221 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.072917 ---- ---- ---- ---- 2441 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.078125 ---- ---- ---- ---- 2431 ---- ---- ---- ---- ---- ---- -221 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.083333 ---- ---- ---- ---- 2331 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.088542 ---- ---- ---- ---1 2331 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.093750 ---- ---- ---- ---- 2331 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.098958 ---- ---- ---- ---- 1331 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.104167 ---- ---- ---- ---1 1331 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.109375 ---- ---- ---- ---- 1221 ---- ---- ---- ---- ---- ---- -121 ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.114583 ---- ---- ---- ---- 1221 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.119792 ---- ---- ---- ---- 1221 ---- ---- ---- ---- ---- ---- -12- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.125000 ---- ---- ---- ---- 1221 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.130208 ---- ---- ---- ---- 1121 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.135417 ---- ---- ---- ---- 1111 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.140625 ---- ---- ---- ---- -111 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.145833 ---- ---- ---- ---- 1111 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.151042 ---- ---- ---- ---- -111 ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.156250 ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.161458 ---- ---- ---- ---- -1-- ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.166667 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -11- ---- ---- ---- ---- ---- ---- ---- --1- ---- ---- ---- --
sec=0.171875 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -1-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.177083 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
sec=0.182292 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
  :

前回注目してた時刻では

前回の「影」の除去OFFの結果
sec=4.682292 0000 0011 3993 2201 0100 0010 0000 0000 0000 0000 0000 0000 0000 0000 1220 0000 0000 0000 0000 0000 0000 0010 0000 00

前回の「影」の除去ONの結果
sec=4.682292 0000 0000 2993 1200 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0220 0000 0000 0000 0000 0000 0000 0010 0000 00

今回の結果 (影除去は常にON)
sec=4.682292 ---- --11 3993 11-- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -22- ---- ---- ---- ---- ---- ---- ---- ---- --

ピーク周辺、かえって若干甘くなったかも?

そして、./jrt2-midi.sh jrt2-mono.raw で、延々と走らせて生成された音声ファイルです。
mix_00.mp3
mix_01.mp3
mix_02.mp3

そんなに大きな変化は無かった感じです。


ベロシティをつけてみる

鍵盤をONする速度(velocity)は、とりあえず100に固定してました。
これをスペクトルの強弱に合わせて変化させてみます。

チャタ除去を経てON判定された時点では、まだ強さを決定できません。
そこからOFF判定されるまでのスペクトルを記録しておいて、 OFF判定された時点で記録さかのぼって調べ、 ONした時点で指定すべき強さを算出しなければなりません。

チャタ除去処理の構造体を継承した、note_onoff構造体を追加します。
ここにabufを追加して強さを記録し、OFF判定時に記録値の平均を求めて、 ベロシティを算出する事にします。

なのでOFF判定時に、ONとOFFのイベントをいっぺんに出力するようになります。
スクリプトの後段のsort処理が、これで決定的に必須となりました。

tool.c

  :

+void
+note_onoff_add(struct note_onoff *nto, double sec, int onoff, double v, int ch)
+{
+	double on_sec, off_sec;
+	int tick, velo;
+
+	abuf_add(&nto->ab, v);
+
+	if(!chatt_add(&nto->ct, sec, onoff)) return;
+
+	if(nto->ct.onoff){
+		nto->on_sec = nto->ct.sec;
+		return;
+	}
+
+	if(nto->on_sec < 0) return;
+
+	on_sec = nto->on_sec;
+	off_sec = nto->ct.sec;
+	nto->on_sec = -1;
+
+	v = abuf_mean(&nto->ab, on_sec, off_sec);
+	
+	velo = (int)(128 * v / nto->pd->max_v);
+	if(velo <= 0) return;
+	if(velo > 127) velo = 127;
+
+	tick = pitdet_sec_to_tick(nto->pd, on_sec);
+	printf("abs=%08d %s ch=%d note=%d velo=%d\n",
+	       tick, "on ", ch, nto->note, velo);
+	
+	tick = pitdet_sec_to_tick(nto->pd, off_sec);
+	printf("abs=%08d %s ch=%d note=%d velo=%d\n",
+	       tick, "off", ch, nto->note, velo);
+
+	fflush(stdout);
+}

そして abuf には平均を出す処理が入ってません。
sbuf は周期的に変化するような複素数前提なので、積分用の作りになってますが、 abuf は正の値の実数ばかりの可能性もあり、誤差の問題があったので避けてきました。
処理の重さが気になりますが、abuf に 平均値を求める処理を追加です。

buf.c

  :

+double
+abuf_mean(struct abuf *ab, double from_sec, double to_sec)
+{
+	int from_i = (int) tbuf_sec_to_sridx(&ab->tb, from_sec);
+	int to_i = (int) tbuf_sec_to_sridx(&ab->tb, to_sec);
+	int i;
+	double sum = 0;
+	if(from_i >= to_i) return 0;
+	for(i=from_i; i<to_i; i++) sum += rbuf_get_dbl(&ab->tb.rb, i);
+	return sum / (to_i - from_i);
+}

また前回まで、しれっと -div 5 で半音5分割で実行してましたが、 各pitchのabufの共通の有効範囲を調べてみると、最新と最古の逆転があり、 -abuf_wsec のデフォルト2.5秒では、足りてない状態でした。

スクリプトでの指定を3秒に増やすとともに、abuf_wsecの長さをチェックするための、 オプション -abuf_wsec_chk を追加しておきます。

あと、スクリプトで指定してるON/OFF判定の閾値 THRES=0.1 も、 多めに拾えるよう半分の 0.05 に下げて試してみます。

--- jrt2-midi.sh-	Wed Aug 26 01:00:00 2015
+++ jrt2-midi.sh	Thu Aug 27 00:00:00 2015
@@ -11,7 +11,9 @@
 fi
 
 DIV=5
+ABUF_WSEC=3
 ADJ_CENT=50
+THRES=0.05
 
 VOL=all:2.5
 CH=0
@@ -58,7 +60,7 @@
 #
 echo Pickup
 if [ ! -e abs_ ] ; then
-  cat $RAW | $TOOL -c 1 -adj_cent $ADJ_CENT -div $DIV -midi -thres 0.1 > abs_
+  cat $RAW | $TOOL -c 1 -adj_cent $ADJ_CENT -div $DIV -abuf_wsec $ABUF_WSEC -midi -thres $THRES > abs_
   cp abs_ abs
 else
   cat abs_ | head -n $(wc -l abs_) > abs

tool9.patch
jrt2-midi.sh-2.patch

$ cat tool9.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-2.patch | patch -p0

$ ./jrt2-midi.sh jrt2-mono.raw
  :

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

-abuf_wsec 指定を増やしたせいか、後半の楽章で低音の再現がまともっぽくなりました。
よーく聴くと、ドラムを再現してる音が遠くで鳴ってます。
末尾の歓声と拍手、放送のナレーションまで再現しようと頑張ってるのが、おもろいです (^_^;


低音のつっこみ対策とコードの整理

前回の波形では、冒頭の低めの弦のような音のタイミングに注意して聴くと、低い音ほど早く鳴り始めています。
左のオリジナルと比べてみても、低い音ほど「つっこみ」が激しくなってます。

それぞれのpitchでのフーリエ変換っぽい処理では、半周期のwn個分の長さのバッファで計算し、 そのバッファの中央の時刻を代表時刻としてました。

なので低い音ほど周期は長く、バッファも長くなります。
代表時刻で鍵盤ONが決定した時は、長〜いバッファの半分の長さ分、先の未来の情報も加味されている事になります。

ではでは...
低い音程、wn を減らしてみてはどうだろうか?
バッファが短かくなれば、先読み分のズレも減るはず。

でもwnの値を減らすと、そのpitchでカバーする周波数の範囲は広がるので、 元々最初に「素」のフーリエ変換でぶちあたった問題に戻るのでは?

元々の「素」フーリエ変換で考えた時は、検出する周波数の低い側の分布の間隔が広過ぎて、ノート番号のそれとはかけ離れていました。
なので、周波数の分布をノート番号に合わせた上で、各周波数でカバーする幅が同じになるように、処理する区間を変えるようにしました。

そこで今度は、低い側の周波数で処理する区間が音符の長さに対して長過るので「区間を短かくしてみては?」という流れになってます。

「分布はノート番号に合わせたままで、低い側のwnを減らす」という事は...
低い側のそれぞれのpitchでカバーする周波数の範囲が広がる。
つまり例えば低めのある周波数の音が鳴っていたとして、一番近いpitchには一番強く検出されるものの、 かなり離れた周波数のpitchでも、割と強めに検出される事になるはずです。

また、低い音で複数の近いノート番号の音が鳴っていると、その辺りを広くカバーしている複数のpitchで検出されるので、 これまでに比べて「ぼやけた」結果になるように思えます。

前者については、最も強く検出されたpitchから順に「影」除去をしているので、 これが上手く機能すれば、割と強めにでてる影も削ってくれて、なんとかなるかもしれません。

後者については、低い音で周波数の近い音をぶつけるケース自体、濁った響きになるので、 検出の精度がぼやけたとしても、まぁまぁ...目をつぶれるのでは?

という事で「だめもと」で試してみましょう。

実装としては、pitdet用にパラメータ lmt_sec を追加します。
デフォルト値は -1 としておいて、0以下の値の場合は従来通りの処理とします。
正の値の場合は、対象pitchの半周期のwn倍の時間が lmt_sec 以下になるまで wn の値を下げるようにしてみます。

pitdetのパラメータが増えてきて、tool.c のコマンド引数から取り出してpitdetに渡すあたりが、ごちゃごちゃしてきました。
pitdetのパラメータはpitdetの初期化で、自身でコマンド引数から取り出すようにまとめてみます。

あとついでに、in.[ch] out.[ch] のパラメータも整理して、 rawmix2.c の2つの入力について、別々のパラメータを指定できるように変更しておきます。

tool10.patch
jrt2-midi.sh-3.patch

今回のパッチは結構広い範囲の修正になってますが、wn の調整箇所は次の差分の箇所になります。

pitdet.c
  :
 		double next_freq = pitdet_note_to_freq(pd, next_note);
 		int wn = (int) pitch_wn(freq, next_freq);
 
-		pitch_init(&pd->arr[j], smp_freq, freq, wn, tick_freq, abuf_wsec);
+		if(pd->lmt_sec > 0) while(wn > 1 && wn / (2 * freq) > pd->lmt_sec) wn--;
+
+		pitch_init(&pd->arr[j], smp_freq, freq, wn, pd->tick_freq, pd->abuf_wsec);
 	}
  : 

パッチをあてて

$ cat tool10.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-3.patch | patch -p0

スペクトルを見てみます。

wnは従来通りで「影」除去なし

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 5 -show -show_w 130 -shadow_cut_en 0 | less
  :
sec=1.526042 000 00-00 ----- 000-0 ----- ----- ----0 ----- -0--0 00000 01100 00000 -0-0- ---0- 0---- --000 0---0 0000- 0-00- -----
sec=1.531250 000 00000 0---- 000-- 0---- ----- -0--- ----- -0-00 00000 01100 00000 ---00 000-- ----0 00-00 -0--0 0000- 00000 ---0-
sec=1.536458 000 00000 0-0-- -00-- ----- ----- ----0 ----- 0--00 00000 01100 000-0 -0-0- 0-00- ----- 0--00 -0--- 0000- -0000 -----
sec=1.541667 000 00000 00000 -00-0 ----- ----- -0--- ----0 0--0- 00000 01100 000-0 -0--- -0-0- ----- --000 0---0 0000- -000- -----
sec=1.546875 000 00000 00000 00000 00--- ----- -0--- ----0 -0-0- 00000 01100 00000 -00-- --0-- --0-- ---00 0-0-0 0000- -000- -----
sec=1.552083 111 11100 00000 00000 00000 0--0- -0--- ----- -0-0- 0-000 01100 00000 -0-0- ---0- ----- --000 0-0-0 0000- -000- -----
sec=1.557292 111 11111 11000 00000 00000 00000 0000- ----- 00--0 0-000 01100 000-0 0---- -0--0 ----- -0000 ----0 0000- -000- -----
sec=1.562500 111 11111 11000 00000 00000 00000 00000 0000- 0---0 00000 01100 000-0 00--- ---0- ----- ---00 0---- -0000 -000- -----
sec=1.567708 111 11111 11100 00000 00000 --000 0--00 00000 00000 0-000 01100 0-0-0 00-0- ---00 ----- 00-00 0---0 -0000 -000- -----
sec=1.572917 111 22211 11000 0000- -0000 000-- -0000 00000 00000 00000 01100 00-0- ----- --0-- ----- -0-00 0---- 0-000 -000- -----
sec=1.578125 111 22211 11100 00000 00000 00000 00000 00000 00000 00000 01100 00000 00-0- 0-00- ----- -0-00 0---- --00- --00- -----
sec=1.583333 112 22221 11000 00000 00000 00-00 00-00 00000 00000 00000 01100 00000 00000 00000 0---- 00-00 0-0-- -000- --0-- -----
sec=1.588542 112 22221 11000 00000 00000 ---00 00000 00000 00000 00000 01000 00000 00000 00000 00000 00000 0-0-0 -000- ----- -----
sec=1.593750 112 22222 110-0 00000 00-00 00000 0000- 00000 00000 00000 -0100 00000 00000 00000 000-0 00000 -0000 0-000 00--- -----
sec=1.598958 012 23222 10000 00000 00000 0000- -0000 00000 01000 00000 00100 000-- 00000 00000 00000 0-000 00000 00000 00-00 0000-
sec=1.604167 012 23322 10000 00000 00000 00000 00000 -0000 11110 00000 01000 00000 00000 00000 000-- ----0 00000 00000 00000 00000
sec=1.609375 012 33321 10000 000-0 0000- 00000 00000 00000 11100 0-000 00100 00000 -0000 00000 00-00 0-00- 00000 00000 00000 00000
sec=1.614583 012 33322 10000 00000 000-- 00000 00000 0-001 11110 00000 00100 00000 00001 11000 0-000 00-00 00000 00000 00000 00000
sec=1.619792 012 33321 00000 00000 0-000 000-- 00000 00000 11110 00000 00000 00-00 0-001 11100 0---- 00000 00000 00000 0-000 00000
sec=1.625000 012 33321 00000 00000 -0000 0-000 0-000 00-01 11110 00000 0000- 00000 00001 11100 00000 0-00- 00000 00000 -0000 000-0
sec=1.630208 012 34321 00000 0000- 000-0 00000 -000- 00000 11110 0000- 00100 0000- 00001 11100 -000- -0000 00000 10000 0000- 00000
sec=1.635417 002 34321 0000- 000-0 00000 00--0 00000 00000 11110 00000 00000 0-000 00001 11100 000-0 00-00 00000 00000 00-00 00000
sec=1.640625 002 34431 00000 00000 00-00 00000 0-000 00000 11100 00000 00000 00000 00001 11100 00--- --000 00000 00000 0-000 000-0
sec=1.645833 002 34421 01000 00000 0-000 0-00- 00000 00000 11100 00000 00000 -0000 00000 11100 0000- -0000 00000 11000 0000- 00000
sec=1.651042 002 34421 010-0 00000 -00-0 00000 00000 00000 12100 00000 00000 00000 00000 11100 00-0- -00-- 00000 11000 0-000 00-0-
sec=1.656250 101 34420 01000 0-000 00000 00-00 -0000 00000 12100 00000 00000 000-0 00000 12100 00000 0-000 00000 11000 0000- 00000
sec=1.661458 102 45420 01000 00000 00000 0-00- 000-0 00000 12100 00000 00000 00000 00000 12100 0000- --000 00000 11000 0000- 0000-
sec=1.666667 101 45420 10000 00000 00-00 -000- 0000- 00000 12100 00000 00000 00000 00000 12100 0-000 00000 00000 11000 -000- 000--
sec=1.671875 001 45420 11000 00000 00000 00000 -0000 0-000 12100 00000 00000 00000 00000 12100 00-00 00000 00000 11000 00000 0--00
sec=1.677083 101 45420 10000 00000 00000 0-000 -0-00 00000 12100 00000 00000 00000 00000 12100 00000 -0-00 00000 11000 00-0- 00-00
sec=1.682292 001 45420 10000 00000 00000 000-0 000-0 -0000 12100 00000 00000 0000- 00000 12100 0000- 00000 00000 1100- 00000 00000
sec=1.687500 001 45420 10000 00000 00000 -0000 00000 00-00 12100 00000 00000 00000 -0000 12100 00--- 00000 -0000 11000 00-00 00000
sec=1.692708 011 35420 10000 0000- 00-00 -0-00 00000 -0-00 12100 00000 0-000 000-0 00000 12100 00000 00000 --0-0 00000 0---0 00-00
sec=1.697917 011 45421 10000 00000 00000 000-0 -00-0 -0000 12100 000-0 00000 0000- 00000 12100 -0000 00000 00000 0000- 00-00 000-0
sec=1.703125 011 35511 10000 000-0 -0000 00000 00000 00-00 12100 00000 00000 0-000 00000 11100 00000 00000 -0-00 10000 00000 0-0--
sec=1.708333 011 36511 10000 00000 00000 -000- 00000 00000 12100 000-0 -0000 00-00 00000 11100 -0000 00000 00-00 000-0 00000 0--00
sec=1.713542 011 36511 10000 00000 00000 0-000 00000 00000 12100 00-00 0-000 00000 00000 01100 000-- 00000 0-000 0000- 00--- -00--
sec=1.718750 011 36511 10000 0000- 00000 00000 000-0 00000 12100 00000 0-000 00000 00-00 01100 00000 --000 ---00 00000 00-00 0-000
sec=1.723958 011 36511 10000 00000 0-000 00000 0-000 0-000 12100 00000 0000- 0000- 00000 01100 000-- -0000 00000 00000 0-00- 00000
sec=1.729167 011 36511 00000 00-00 00000 00000 00-00 00000 12100 0-000 00000 00000 00000 01100 00000 0000- 0-000 00000 00-00 00000
sec=1.734375 011 36511 10000 00000 00000 00000 00000 00000 12100 00000 --000 00000 00000 01100 000-- -0000 0-0-0 00000 00--0 --000
sec=1.739583 011 36511 00000 00000 00000 000-0 00000 00000 11100 0000- -0000 00-00 00000 01100 000-0 0000- 0---0 00000 00000 000--
sec=1.744792 011 36511 00000 00000 00000 0000- 00000 00000 01100 00000 00000 00--- -0000 01100 0-0-0 0000- -0-00 00000 --00- -0000
sec=1.750000 011 35411 10000 00000 00000 0-000 00000 00000 01100 00000 00000 0-0-0 0-000 01100 00000 0-000 -0-00 00000 -000- 0--0-
sec=1.755208 011 35411 00000 00000 00000 00000 0-000 00000 01100 000-0 0000- 0---0 00000 00000 00000 0--00 00-00 00000 000-- -----
sec=1.760417 011 35411 00000 00000 00000 00000 0000- 00000 01100 0000- -0000 -0-00 0-000 0000- 000-0 00000 00000 00000 000-- --0--
sec=1.765625 011 25410 00000 00000 00000 0000- 00000 00000 01100 0000- -0000 0-00- 000-0 00000 00000 0-0-0 --00- 00000 -000- ---00
sec=1.770833 011 25410 00000 00000 00000 00000 00-00 00-00 01100 00000 00000 00-0- -00-- 00000 0---- 00-00 00000 00000 --00- 00--0
sec=1.776042 011 24410 00000 00000 00000 000-0 00000 00000 01100 00-00 00000 00000 0-000 00000 000-0 00-0- --00- 00-00 00000 00--0
sec=1.781250 011 25410 00000 00000 -0000 00-00 00000 00000 01100 00000 -0000 000-- 00000 00000 ----- ----0 0-000 000-- ----- ---00
sec=1.786458 000 24310 00000 00000 0000- -0-00 00000 00000 01100 00000 -0000 00-00 00000 00000 0---0 00-00 -0000 00000 00000 0000-
sec=1.791667 011 24410 00000 00000 0000- 0000- 00000 00000 01100 0-00- 00000 0--0- 00000 00000 0---- -0--- -00-0 00000 -00-0 -0000
sec=1.796875 000 24310 00000 -0000 00000 0000- 00-0- 00000 00100 000-0 0000- 0-00- ---00 00000 ----- ----0 -0000 000-0 ----0 0---0
sec=1.802083 001 24310 00000 00000 00000 00000 00--0 00000 01100 00000 00000 --0-0 00000 00000 -0-0- 0000- -000- 0000- ----- -----
sec=1.807292 000 24310 00000 00-00 00000 -0--- -0000 00000 00000 00-00 00000 -000- 0-00- 00000 -00-0 --00- 0-000 000-0 -000- -----
sec=1.812500 001 23310 00000 00000 -000- 0000- ----0 00000 00100 0000- 00000 000-0 000-- 00000 -00-0 --000 --000 000-0 -00-- ---00
sec=1.817708 000 13310 00000 00000 00000 00000 00000 000-- 00000 000-0 0-00- 000-0 --0-- 000-0 0---- -000- 00000 00000 -00-- 00---
sec=1.822917 000 13310 00000 0000- 00000 00-00 000-0 00000 00000 00--0 0-000 00-00 0--00 00000 000-- --000 --000 000-0 000-- 00---
sec=1.828125 000 13310 00000 00000 00000 00000 00--- 000-0 00000 0000- 00-0- 00--- --000 00000 ----- --000 0-000 000-0 0000- ----0
sec=1.833333 000 13310 00000 00000 0000- 0---0 00000 00000 00000 00-0- 00000 0-000 000-0 000-0 00--- 00000 --000 00000 --000 0000-
sec=1.838542 000 13311 00000 00000 -000- 0000- 0-000 00-00 00000 -0000 00000 --00- -0000 00000 0---- --000 -0000 000-0 -0000 ----0
sec=1.843750 000 13210 00000 -0-0- 00000 00-0- 00-00 --0-0 00000 -00-0 0000- 0-000 0---- 0000- ----- --000 -0000 000-0 00000 00---
sec=1.848958 000 12210 00000 000-0 -0000 -0-00 00-00 00000 00000 000-0 00000 -00-- --0-- 000-- ----- 00000 --000 0000- -000- 0----
sec=1.854167 000 12210 00000 000-0 00000 00--0 0000- -0--- 00000 -0-00 00000 -000- 0---- 000-- -00-- --00- --000 0000- -000- -----
sec=1.859375 000 12210 00000 00000 0000- 0000- 00-00 -0000 00000 0-0-- 00000 00--- -0--- 0000- -0--0 --000 00--0 0000- -00-- 0----
sec=1.864583 000 12210 00000 0000- 00000 00000 0000- 0-000 00000 000-0 0000- 000-- --0-- -00-- ----- --000 --0-0 0000- 000-- 00---
  :

「影」除去ありにすると

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 5 -show -show_w 130 -shadow_cut_en 1 | less
  :
sec=1.526042 000 00-00 ----- 000-0 ----- ----- ----0 ----- -0--0 00000 01100 00000 -0-0- ---0- 0---- --000 0---0 0000- 0-00- -----
sec=1.531250 000 00000 0---- 000-- 0---- ----- -0--- ----- -0-00 00000 01100 00000 ---00 000-- ----0 00-00 -0--0 0000- 00000 ---0-
sec=1.536458 000 00000 0-0-- -00-- ----- ----- ----0 ----- 0--00 00000 01100 000-0 -0-0- 0-00- ----- 0--00 -0--- 0000- -0000 -----
sec=1.541667 000 00000 00000 -00-0 ----- ----- -0--- ----0 0--0- 00000 01100 000-0 -0--- -0-0- ----- --000 0---0 0000- -000- -----
sec=1.546875 000 00000 00000 00000 00--- ----- -0--- ----0 -0-0- 00000 01100 00000 -00-- --0-- --0-- ---00 0-0-0 0000- -000- -----
sec=1.552083 011 10000 00000 00000 00000 0--0- -0--- ----- -0-0- 0-000 01100 00000 -0-0- ---0- ----- --000 0-0-0 0000- -000- -----
sec=1.557292 011 11110 00000 00000 00000 00000 0000- ----- 00--0 0-000 01100 000-0 0---- -0--0 ----- -0000 ----0 0000- -000- -----
sec=1.562500 111 11110 00000 00000 00000 00000 00000 0000- 0---0 00000 00100 000-0 00--- ---0- ----- ---00 0---- -0000 -000- -----
sec=1.567708 011 11111 10000 00000 00000 --000 0--00 00000 00000 0-000 00000 0-0-0 00-0- ---00 ----- 00-00 0---0 -0000 -000- -----
sec=1.572917 111 21111 00000 0000- -0000 000-- -0000 00000 00000 00000 00100 00-0- ----- --0-- ----- -0-00 0---- 0-000 -000- -----
sec=1.578125 011 21211 10000 00000 00000 00000 00000 00000 00000 00000 00100 00000 00-0- 0-00- ----- -0-00 0---- --00- --00- -----
sec=1.583333 012 22221 10000 00000 00000 00-00 00-00 00000 00000 00000 01000 00000 00000 00000 0---- 00-00 0-0-- -000- --0-- -----
sec=1.588542 012 22221 10000 00000 00000 ---00 00000 00000 00000 00000 01000 00000 00000 00000 00000 00000 0-0-0 -000- ----- -----
sec=1.593750 012 22221 100-0 00000 00-00 00000 0000- 00000 00000 00000 -0100 00000 00000 00000 000-0 00000 -0000 0-000 00--- -----
sec=1.598958 012 22221 00000 00000 00000 0000- -0000 00000 00000 00000 00000 000-- 00000 00000 00000 0-000 00000 00000 00-00 0000-
sec=1.604167 012 23321 00000 00000 00000 00000 00000 -0000 01000 00000 00000 00000 00000 00000 000-- ----0 00000 00000 00000 00000
sec=1.609375 012 33321 00000 000-0 0000- 00000 00000 00000 00100 0-000 00000 00000 -0000 00000 00-00 0-00- 00000 00000 00000 00000
sec=1.614583 012 33321 00000 00000 000-- 00000 00000 0-000 11100 00000 00000 00000 00000 11000 0-000 00-00 00000 00000 00000 00000
sec=1.619792 012 33321 00000 00000 0-000 000-- 00000 00000 11100 00000 00000 00-00 0-000 11000 0---- 00000 00000 00000 0-000 00000
sec=1.625000 002 33321 00000 00000 -0000 0-000 0-000 00-00 11100 00000 0000- 00000 00000 11000 00000 0-00- 00000 00000 -0000 000-0
sec=1.630208 002 34321 00000 0000- 000-0 00000 -000- 00000 11100 0000- 00000 0000- 00000 11100 -000- -0000 00000 00000 0000- 00000
sec=1.635417 002 34320 0000- 000-0 00000 00--0 00000 00000 11100 00000 00000 0-000 00000 11000 000-0 00-00 00000 00000 00-00 00000
sec=1.640625 002 34420 00000 00000 00-00 00000 0-000 00000 11100 00000 00000 00000 00000 11100 00--- --000 00000 00000 0-000 000-0
sec=1.645833 001 34420 00000 00000 0-000 0-00- 00000 00000 11100 00000 00000 -0000 00000 11100 0000- -0000 00000 00000 0000- 00000
sec=1.651042 002 34420 000-0 00000 -00-0 00000 00000 00000 11100 00000 00000 00000 00000 11100 00-0- -00-- 00000 00000 0-000 00-0-
sec=1.656250 001 34420 00000 0-000 00000 00-00 -0000 00000 11100 00000 00000 000-0 00000 11100 00000 0-000 00000 00000 0000- 00000
sec=1.661458 001 35420 00000 00000 00000 0-00- 000-0 00000 12100 00000 00000 00000 00000 11100 0000- --000 00000 11000 0000- 0000-
sec=1.666667 001 35420 00000 00000 00-00 -000- 0000- 00000 12100 00000 00000 00000 00000 12100 0-000 00000 00000 11000 -000- 000--
sec=1.671875 001 45420 00000 00000 00000 00000 -0000 0-000 12100 00000 00000 00000 00000 12100 00-00 00000 00000 10000 00000 0--00
sec=1.677083 001 45420 00000 00000 00000 0-000 -0-00 00000 12100 00000 00000 00000 00000 12100 00000 -0-00 00000 11000 00-0- 00-00
sec=1.682292 001 35410 00000 00000 00000 000-0 000-0 -0000 12100 00000 00000 0000- 00000 12100 0000- 00000 00000 0100- 00000 00000
sec=1.687500 001 35410 00000 00000 00000 -0000 00000 00-00 12100 00000 00000 00000 -0000 12100 00--- 00000 -0000 11000 00-00 00000
sec=1.692708 001 35410 00000 0000- 00-00 -0-00 00000 -0-00 12100 00000 0-000 000-0 00000 12100 00000 00000 --0-0 00000 0---0 00-00
sec=1.697917 000 35410 00000 00000 00000 000-0 -00-0 -0000 12100 000-0 00000 0000- 00000 11100 -0000 00000 00000 0000- 00-00 000-0
sec=1.703125 000 35410 00000 000-0 -0000 00000 00000 00-00 12100 00000 00000 0-000 00000 01100 00000 00000 -0-00 00000 00000 0-0--
sec=1.708333 000 36410 00000 00000 00000 -000- 00000 00000 12100 000-0 -0000 00-00 00000 01100 -0000 00000 00-00 000-0 00000 0--00
sec=1.713542 000 36510 00000 00000 00000 0-000 00000 00000 11100 00-00 0-000 00000 00000 01100 000-- 00000 0-000 0000- 00--- -00--
sec=1.718750 000 36511 00000 0000- 00000 00000 000-0 00000 12100 00000 0-000 00000 00-00 01100 00000 --000 ---00 00000 00-00 0-000
sec=1.723958 000 36511 00000 00000 0-000 00000 0-000 0-000 12100 00000 0000- 0000- 00000 01100 000-- -0000 00000 00000 0-00- 00000
sec=1.729167 000 36511 00000 00-00 00000 00000 00-00 00000 12100 0-000 00000 00000 00000 01100 00000 0000- 0-000 00000 00-00 00000
sec=1.734375 001 36511 00000 00000 00000 00000 00000 00000 11100 00000 --000 00000 00000 01100 000-- -0000 0-0-0 00000 00--0 --000
sec=1.739583 001 36400 00000 00000 00000 000-0 00000 00000 01100 0000- -0000 00-00 00000 01000 000-0 0000- 0---0 00000 00000 000--
sec=1.744792 011 36410 00000 00000 00000 0000- 00000 00000 01100 00000 00000 00--- -0000 00000 0-0-0 0000- -0-00 00000 --00- -0000
sec=1.750000 001 35400 00000 00000 00000 0-000 00000 00000 01100 00000 00000 0-0-0 0-000 00000 00000 0-000 -0-00 00000 -000- 0--0-
sec=1.755208 001 35410 00000 00000 00000 00000 0-000 00000 01100 000-0 0000- 0---0 00000 00000 00000 0--00 00-00 00000 000-- -----
sec=1.760417 000 25410 00000 00000 00000 00000 0000- 00000 01100 0000- -0000 -0-00 0-000 0000- 000-0 00000 00000 00000 000-- --0--
sec=1.765625 001 25410 00000 00000 00000 0000- 00000 00000 01100 0000- -0000 0-00- 000-0 00000 00000 0-0-0 --00- 00000 -000- ---00
sec=1.770833 001 25410 00000 00000 00000 00000 00-00 00-00 01100 00000 00000 00-0- -00-- 00000 0---- 00-00 00000 00000 --00- 00--0
sec=1.776042 001 24410 00000 00000 00000 000-0 00000 00000 01100 00-00 00000 00000 0-000 00000 000-0 00-0- --00- 00-00 00000 00--0
sec=1.781250 000 25410 00000 00000 -0000 00-00 00000 00000 01100 00000 -0000 000-- 00000 00000 ----- ----0 0-000 000-- ----- ---00
sec=1.786458 000 24310 00000 00000 0000- -0-00 00000 00000 01100 00000 -0000 00-00 00000 00000 0---0 00-00 -0000 00000 00000 0000-
sec=1.791667 000 24310 00000 00000 0000- 0000- 00000 00000 00100 0-00- 00000 0--0- 00000 00000 0---- -0--- -00-0 00000 -00-0 -0000
sec=1.796875 000 24310 00000 -0000 00000 0000- 00-0- 00000 00000 000-0 0000- 0-00- ---00 00000 ----- ----0 -0000 000-0 ----0 0---0
sec=1.802083 000 24310 00000 00000 00000 00000 00--0 00000 00000 00000 00000 --0-0 00000 00000 -0-0- 0000- -000- 0000- ----- -----
sec=1.807292 000 14310 00000 00-00 00000 -0--- -0000 00000 00000 00-00 00000 -000- 0-00- 00000 -00-0 --00- 0-000 000-0 -000- -----
sec=1.812500 000 13310 00000 00000 -000- 0000- ----0 00000 00000 0000- 00000 000-0 000-- 00000 -00-0 --000 --000 000-0 -00-- ---00
sec=1.817708 000 13310 00000 00000 00000 00000 00000 000-- 00000 000-0 0-00- 000-0 --0-- 000-0 0---- -000- 00000 00000 -00-- 00---
sec=1.822917 000 13310 00000 0000- 00000 00-00 000-0 00000 00000 00--0 0-000 00-00 0--00 00000 000-- --000 --000 000-0 000-- 00---
sec=1.828125 000 13310 00000 00000 00000 00000 00--- 000-0 00000 0000- 00-0- 00--- --000 00000 ----- --000 0-000 000-0 0000- ----0
sec=1.833333 000 13210 00000 00000 0000- 0---0 00000 00000 00000 00-0- 00000 0-000 000-0 000-0 00--- 00000 --000 00000 --000 0000-
sec=1.838542 000 13310 00000 00000 -000- 0000- 0-000 00-00 00000 -0000 00000 --00- -0000 00000 0---- --000 -0000 000-0 -0000 ----0
sec=1.843750 000 12210 00000 -0-0- 00000 00-0- 00-00 --0-0 00000 -00-0 0000- 0-000 0---- 0000- ----- --000 -0000 000-0 00000 00---
sec=1.848958 000 12210 00000 000-0 -0000 -0-00 00-00 00000 00000 000-0 00000 -00-- --0-- 000-- ----- 00000 --000 0000- -000- 0----
sec=1.854167 000 12200 00000 000-0 00000 00--0 0000- -0--- 00000 -0-00 00000 -000- 0---- 000-- -00-- --00- --000 0000- -000- -----
sec=1.859375 000 12200 00000 00000 0000- 0000- 00-00 -0000 00000 0-0-- 00000 00--- -0--- 0000- -0--0 --000 00--0 0000- -00-- 0----
sec=1.864583 000 12200 00000 0000- 00000 00000 0000- 0-000 00000 000-0 0000- 000-- --0-- -00-- ----- --000 --0-0 0000- 000-- 00---
  :

では、wnをlmt_secで制限してみます。
チャタリング除去のlmt_cutと同じく、1小節2秒として32分音符の長さ
2/32 = 0.0625秒を指定してみます。

wn を lmt_sec 0.0625 で制限して「影」除去なし

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 5 -show -show_w 130 -shadow_cut_en 0 -pd_lmt_sec 0.0625 | less
  :
sec=1.526042 000 ---0- -0--- ----0 ----0 0---- 00--- 00--0 00--0 00000 11100 00000 000-0 ----- ----- ---00 ----0 0000- 00-00 -----
sec=1.531250 000 --000 0--00 000-- 000-- -0-0- ----- ----- 0-000 00000 01100 00000 00-00 --00- -0--0 --000 ----0 0000- 000-- -----
sec=1.536458 000 --000 00-00 000-- 00--- 000-- 0000- -000- 00000 00000 01100 0000- 00--0 ----- ----- -0000 0--00 0000- 000-- -----
sec=1.541667 000 0-000 00--- 0---0 000-- -000- -00-- 000-- 00000 00000 01100 0000- 00--- ----- ----- --000 0-00- 0000- -000- -----
sec=1.546875 000 0--00 00--0 000-- 0000- -000- -000- -00-- 000-- 00000 00000 0000- 00--- ---0- ----- --000 ----- -000- --00- -----
sec=1.552083 000 0--00 00--0 0000- -00-- -00-- -000- -000- 000-0 00000 00000 0000- ----- ----- ----- -0000 00--- -000- --00- -----
sec=1.557292 000 0--00 000-- 00--- 0000- --0-- --0-- -00-- -0--0 00000 00000 0000- 00--- --0-- ----- --000 0---- -0000 --00- -----
sec=1.562500 000 0--00 000-0 000-- ----- -00-- -000- --0-- -0000 00000 00000 -000- 00--- ----- ----- ---00 0---- --000 -00-- -----
sec=1.567708 000 0--00 00--0 0000- -000- ---0- ----- -00-- 00000 00000 00000 0000- 00--- ----- ----- --000 0---- --000 -00-- -----
sec=1.572917 000 0---0 00--- ----- -0--- -00-- -0000 --00- --00- -0000 00000 0-00- -0--- --000 ----- ---00 0---- -000- ----- -----
sec=1.578125 000 0--00 000-- -00-- ----- ----- ----- ----- ----- -0000 00000 0-00- ----- ----- 0---- ---00 00-0- -00-- ----- -----
sec=1.583333 000 0--00 00--- 000-- ----- ----- -00-- ----- ----- 00000 00000 0-00- -0--0 --000 -0--0 0-000 0---- -000- ----- -----
sec=1.588542 000 0--00 00--- 000-- ----- ----- 0---- ----- ----- 000-0 00000 0-00- 00--0 --000 ----- ---00 0---- --00- ----- -----
sec=1.593750 000 0--00 00--- ----- 00--- ----0 000-- ----- -0--0 000-0 00000 0-00- -0--- ----- 0---- ---00 0---- -00-- ----- -----
sec=1.598958 000 0---0 00--- -000- --00- --000 00--- ----- ----- 000-0 00000 000-- 0--00 -000- ----0 ---00 ----- -000- ----- -----
sec=1.604167 000 00000 00000 00000 00000 00000 00000 00000 00000 00000 01100 00000 00000 00000 00000 00000 00000 0-000 00000 00000
sec=1.609375 111 11100 01000 00000 00000 00000 00000 00000 00000 00000 01100 00000 00000 00000 00000 0-000 00000 00000 00000 000-0
sec=1.614583 111 11111 11111 11111 10000 00000 00000 00001 11000 00000 00000 00000 00000 00000 00000 000-0 00000 00000 00--- 00000
sec=1.619792 111 22221 11111 11000 00000 0---- 00000 00000 00000 00000 00000 00000 00000 00000 0000- ---00 00000 00000 0--00 00--0
sec=1.625000 222 22233 22221 11100 00000 00000 00000 01111 11000 00000 00000 00000 00000 11000 00000 00000 00000 00000 00000 00000
sec=1.630208 333 22222 22211 00000 00000 00000 00000 00001 11111 00000 00000 00000 00000 11000 00000 000-0 00000 00000 -000- 000-0
sec=1.635417 334 44333 32211 00000 00000 0-000 00000 00001 11111 10000 00000 00000 00011 11100 0-0-- -00-0 00000 00000 -00-- 00--0
sec=1.640625 333 44333 32211 0-000 00000 00000 00000 00001 11111 10-00 00000 00000 00001 11100 00000 00000 00000 00000 0-000 -00-0
sec=1.645833 334 44443 32100 00000 00-00 0000- 00000 00011 11111 00000 00000 00000 00001 11100 -000- -000- 00000 00000 00-00 -00-0
sec=1.651042 234 55554 32100 01100 00000 00000 00000 00001 22211 00000 00000 00000 00001 11100 000-0 0-00- 00000 00000 0-00- 00-0-
sec=1.656250 345 55554 32100 11000 00000 00000 00000 0-001 12211 00000 00000 00000 00001 11100 000-0 --000 00000 00000 0-00- 0000-
sec=1.661458 245 56654 32001 11000 00000 00000 00000 00-01 22210 00000 00000 00000 00001 12100 00-00 -000- 00000 11000 00-00 -0-0-
sec=1.666667 345 55554 32100 11000 00000 00000 00000 00011 11110 0-000 00000 00000 00000 1110- 00-00 -0000 00000 01000 000-0 ---00
sec=1.671875 245 66654 31000 11000 00000 00000 00000 00-11 22110 00000 00000 00000 00000 11100 00000 00000 -0000 00000 0000- -----
sec=1.677083 234 55554 32100 11100 00000 00000 00000 00011 11110 00000 00000 0-000 00000 11100 00000 00000 00000 10000 -0--0 -0---
sec=1.682292 234 56654 21001 10000 00000 00000 00000 00001 12110 00000 00000 --000 000-0 11100 00-00 -0000 0--00 000-0 0-0-0 0-0-0
sec=1.687500 234 45443 21100 11000 00000 -0000 00000 00001 11210 -0000 00000 00-00 00000 11110 00000 0000- 00000 00000 00-00 00000
sec=1.692708 234 55543 21001 11000 00000 000-- 000-0 -0000 11111 00000 000-0 00000 00000 11110 00000 00000 00000 0000- 00000 00000
sec=1.697917 234 55443 21000 11000 00000 0000- 000-0 00001 12110 00000 00000 0-000 00000 1110- 00-0- 00000 -0000 000-0 0-0-- ----0
sec=1.703125 234 55543 21001 11000 00000 00000 00000 -0001 11110 00000 -0000 00000 00000 11100 00000 00000 00000 000-0 00--0 -----
sec=1.708333 234 45443 21001 11000 00000 0000- 00000 00001 11110 -0000 00000 00000 -0000 11100 00--0 --000 ---00 000-0 -0000 -0-0-
sec=1.713542 233 44443 21001 11000 00100 00000 00000 00001 11110 00000 000-0 000-0 00000 01100 -00-- 00000 0-000 000-0 0-0-- -----
sec=1.718750 234 44443 21001 11000 0000- 00000 00000 00001 11110 00000 00000 0-00- 00000 01000 -00-- 0-000 000-0 00000 00-00 -0-0-
sec=1.723958 233 44433 21000 11000 00000 00000 00000 00001 11100 0-000 00000 00000 00000 01100 00000 00000 -0000 0000- -00-- ----0
sec=1.729167 234 45433 21000 00000 00000 00000 00000 00-01 11100 000-- 00000 00-00 00000 00100 00000 0--00 00-00 000-- 0-000 0----
sec=1.734375 233 34333 21000 00000 00000 00000 00000 00001 11100 00000 00000 00--0 -0000 00000 00000 00000 00000 00000 -0000 -0-0-
sec=1.739583 223 44433 21000 00000 00000 00000 0--00 00000 11110 00000 0--00 00-00 --000 00000 00000 00000 -0-00 00000 0-000 00-00
sec=1.744792 123 33333 21000 00000 00000 0000- 00000 -0000 11110 -000- 00000 0-000 -0000 0000- 00--0 0--00 -0000 0000- 0000- -----
sec=1.750000 123 34433 21000 00000 00000 00000 -0000 00000 01110 00000 00000 00000 00-00 00000 00000 00000 00000 00--0 --000 --0-0
sec=1.755208 123 33332 21000 00000 00000 00000 00000 00000 11110 0000- 00000 00000 -0000 00000 000-0 -000- 0--00 000-0 0-00- -----
sec=1.760417 123 33332 21000 00000 00000 0-000 -0000 00000 00000 00-00 00000 00000 00000 00000 ----- ---00 0-000 000-0 -00-- -----
sec=1.765625 123 33332 21000 00000 00000 00000 000-0 00000 11110 0-00- 00000 0-000 -00-0 00000 --0-- ----0 0-00- 000-0 -0000 -0---
sec=1.770833 122 33332 21000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 -0-00 -00-0 000-- 0000- 0---- 0-0-0
sec=1.776042 122 33332 21000 000-0 00000 00000 000-0 00000 01100 000-0 00000 000-- --000 00000 -0000 0-0-- 000-- 0000- 00-0- -----
sec=1.781250 122 23332 21000 00000 00010 00000 00000 00000 00000 0-000 00000 -0-00 ---00 00000 000-- 0---0 00000 00000 -0-0- -0-0-
sec=1.786458 122 33332 11000 00000 00000 0-000 000-0 00000 00000 00000 00000 000-- --0-0 00000 000-0 --00- --000 000-- ----0 -----
sec=1.791667 122 22222 1100- 00000 00000 00000 -0000 00000 00100 0-000 00000 0--00 --0-0 0000- 00--0 0-0-0 0-000 0000- ---0- ---0-
sec=1.796875 112 33322 11000 00000 00000 00000 000-- -00-0 00000 00000 00000 000-- 00-00 00000 00000 000-0 0--00 0000- -00-0 -----
sec=1.802083 112 23322 11000 00000 00000 0000- 0000- 00000 00000 0000- 00000 00-00 -0000 00000 0---- -00-- --000 0000- 0---- -----
sec=1.807292 112 22322 11000 00000 00000 00000 0-000 00000 00000 00000 0---- -0000 00000 0000- ----- --00- 0--00 000-- 0---- -----
sec=1.812500 112 22222 110-0 00000 00000 00000 -000- 000-0 00000 0000- -0000 0-00- 000-0 00000 ----- --00- --000 000-- -00-0 -----
sec=1.817708 112 22221 11000 00000 00000 00000 00000 00000 00000 00000 00000 00000 000-0 0000- ----- --000 -0000 000-- 0-00- 0----
sec=1.822917 112 22221 10000 00000 00000 00000 00-00 00-00 00000 0---0 0--00 00000 00--0 0000- 000-0 00000 -000- 0000- 0000- -----
sec=1.828125 112 22221 10000 00000 00000 0-000 0-000 0-000 00000 0-000 -0000 00--- ----0 00000 000-- 0--00 00000 00000 00--- -0---
sec=1.833333 112 22221 10000 00000 00000 0-0-- 0-0-0 0--00 00000 00--- 00-00 00000 0-0-0 0000- -0--- -000- --000 0000- 00--- -----
sec=1.838542 111 11111 10000 00000 00000 00000 00000 -0000 00000 -0000 00000 00--0 --0-- 00000 -00-- --00- --000 0000- 0000- -0---
sec=1.843750 011 22211 10000 00000 00000 000-0 000-- 00000 00000 00000 00000 00000 00--0 00000 --000 ---00 ---00 00000 ---00 ----0
sec=1.848958 011 11111 00000 00000 00000 00000 0-0-- 00000 00000 00000 --000 00--- -00-0 0000- --0-- --000 0---0 0000- -00-- -----
sec=1.854167 011 11111 00000 00000 00000 0--00 00000 0-000 00000 00--0 00000 00000 0---0 00000 ----- --000 ----0 0000- --00- -----
sec=1.859375 011 11111 00000 00000 00000 000-- 000-- 000-0 00000 000-0 00000 0-000 --0-- 0000- ----- --000 ---00 0000- ----- -----
sec=1.864583 011 11111 10000 00000 00000 00000 00000 0-000 00000 --000 00000 0000- -0--0 0000- ----- --000 ----0 00000 -00-- -----
  :

山が動いた。
時間的に後ろにズレました。

「影」除去ONにしてみると

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 5 -show -show_w 130 -shadow_cut_en 1 -pd_lmt_sec 0.0625 | less
  :
sec=1.526042 000 ---0- -0--- ----0 ----0 0---- 00--- 00--0 00--0 00000 01000 00000 000-0 ----- ----- ---00 ----0 0000- 00-00 -----
sec=1.531250 000 --000 0--00 000-- 000-- -0-0- ----- ----- 0-000 00000 01000 00000 00-00 --00- -0--0 --000 ----0 0000- 000-- -----
sec=1.536458 000 --000 00-00 000-- 00--- 000-- 0000- -000- 00000 00000 01000 0000- 00--0 ----- ----- -0000 0--00 0000- 000-- -----
sec=1.541667 000 0-000 00--- 0---0 000-- -000- -00-- 000-- 00000 00000 01000 0000- 00--- ----- ----- --000 0-00- 0000- -000- -----
sec=1.546875 000 0--00 00--0 000-- 0000- -000- -000- -00-- 000-- 00000 00000 0000- 00--- ---0- ----- --000 ----- -000- --00- -----
sec=1.552083 000 0--00 00--0 0000- -00-- -00-- -000- -000- 000-0 00000 00000 0000- ----- ----- ----- -0000 00--- -000- --00- -----
sec=1.557292 000 0--00 000-- 00--- 0000- --0-- --0-- -00-- -0--0 00000 00000 0000- 00--- --0-- ----- --000 0---- -0000 --00- -----
sec=1.562500 000 0--00 000-0 000-- ----- -00-- -000- --0-- -0000 00000 00000 -000- 00--- ----- ----- ---00 0---- --000 -00-- -----
sec=1.567708 000 0--00 00--0 0000- -000- ---0- ----- -00-- 00000 00000 00000 0000- 00--- ----- ----- --000 0---- --000 -00-- -----
sec=1.572917 000 0---0 00--- ----- -0--- -00-- -0000 --00- --00- -0000 00000 0-00- -0--- --000 ----- ---00 0---- -000- ----- -----
sec=1.578125 000 0--00 000-- -00-- ----- ----- ----- ----- ----- -0000 00000 0-00- ----- ----- 0---- ---00 00-0- -00-- ----- -----
sec=1.583333 000 0--00 00--- 000-- ----- ----- -00-- ----- ----- 00000 00000 0-00- -0--0 --000 -0--0 0-000 0---- -000- ----- -----
sec=1.588542 000 0--00 00--- 000-- ----- ----- 0---- ----- ----- 000-0 00000 0-00- 00--0 --000 ----- ---00 0---- --00- ----- -----
sec=1.593750 000 0--00 00--- ----- 00--- ----0 000-- ----- -0--0 000-0 00000 0-00- -0--- ----- 0---- ---00 0---- -00-- ----- -----
sec=1.598958 000 0---0 00--- -000- --00- --000 00--- ----- ----- 000-0 00000 000-- 0--00 -000- ----0 ---00 ----- -000- ----- -----
sec=1.604167 000 00000 00000 00000 00000 00000 00000 00000 00000 00000 01000 00000 00000 00000 00000 00000 00000 0-000 00000 00000
sec=1.609375 000 01000 00000 00000 00000 00000 00000 00000 00000 00000 00100 00000 00000 00000 00000 0-000 00000 00000 00000 000-0
sec=1.614583 000 10010 00100 10000 00000 00000 00000 00000 10000 00000 00000 00000 00000 00000 00000 000-0 00000 00000 00--- 00000
sec=1.619792 001 00100 01001 00000 00000 0---- 00000 00000 00000 00000 00000 00000 00000 00000 0000- ---00 00000 00000 0--00 00--0
sec=1.625000 200 02-03 0-100 10000 00000 00000 00000 00000 01000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000
sec=1.630208 020 02002 00100 00000 00000 00000 00000 00000 00100 00000 00000 00000 00000 00000 00000 000-0 00000 00000 -000- 000-0
sec=1.635417 003 --30- 20000 00000 00000 0-000 00000 00000 01000 00000 00000 00000 00000 01000 0-0-- -00-0 00000 00000 -00-- 00--0
sec=1.640625 02- 04-03 00100 0-000 00000 00000 00000 00000 01010 00-00 00000 00000 00000 01000 00000 00000 00000 00000 0-000 -00-0
sec=1.645833 003 --40- 20000 00000 00-00 0000- 00000 00000 01000 00000 00000 00000 00000 01000 -000- -000- 00000 00000 00-00 -00-0
sec=1.651042 003 --50- 20000 00000 00000 00000 00000 00000 01010 00000 00000 00000 00000 01000 000-0 0-00- 00000 00000 0-00- 00-0-
sec=1.656250 004 --50- 20000 00000 00000 00000 00000 0-000 01010 00000 00000 00000 00000 01000 000-0 --000 00000 00000 0-00- 0000-
sec=1.661458 02- 05-03 --000 00000 00000 00000 00000 00-00 02010 00000 00000 00000 00000 01000 00-00 -000- 00000 00000 00-00 -0-0-
sec=1.666667 02- 05--3 --000 00000 00000 00000 00000 00001 01000 0-000 00000 00000 00000 0100- 00-00 -0000 00000 00000 000-0 ---00
sec=1.671875 02- 06--3 0-000 00000 00000 00000 00000 00-01 02000 00000 00000 00000 00000 01000 00000 00000 -0000 00000 0000- -----
sec=1.677083 02- 05-03 0-000 00000 00000 00000 00000 00001 01000 00000 00000 0-000 00000 01000 00000 00000 00000 00000 -0--0 -0---
sec=1.682292 02- 06--3 --000 00000 00000 00000 00000 00001 01000 00000 00000 --000 000-0 01000 00-00 -0000 0--00 000-0 0-0-0 0-0-0
sec=1.687500 02- 04-03 --000 00000 00000 -0000 00000 00000 01010 -0000 00000 00-00 00000 01000 00000 0000- 00000 00000 00-00 00000
sec=1.692708 02- 05--3 -0000 00000 00000 000-- 000-0 -0000 01010 00000 000-0 00000 00000 01000 00000 00000 00000 0000- 00000 00000
sec=1.697917 02- 05-03 --000 00000 00000 0000- 000-0 00000 01010 00000 00000 0-000 00000 0100- 00-0- 00000 -0000 000-0 0-0-- ----0
sec=1.703125 02- 05-03 -0000 00000 00000 00000 00000 -0000 01010 00000 -0000 00000 00000 01000 00000 00000 00000 000-0 00--0 -----
sec=1.708333 003 --400 1-000 00000 00000 0000- 00000 00000 01000 -0000 00000 00000 -0000 01000 00--0 --000 ---00 000-0 -0000 -0-0-
sec=1.713542 02- 04-03 0-000 00000 00000 00000 00000 00000 01000 00000 000-0 000-0 00000 01000 -00-- 00000 0-000 000-0 0-0-- -----
sec=1.718750 02- 04002 0-000 00000 0000- 00000 00000 00000 01000 00000 00000 0-00- 00000 00000 -00-- 0-000 000-0 00000 00-00 -0-0-
sec=1.723958 02- 04-02 00000 00000 00000 00000 00000 00000 01000 0-000 00000 00000 00000 01000 00000 00000 -0000 0000- -00-- ----0
sec=1.729167 02- 050-2 00000 00000 00000 00000 00000 00-01 01000 000-- 00000 00-00 00000 00000 00000 0--00 00-00 000-- 0-000 0----
sec=1.734375 02- 04-02 00000 00000 00000 00000 00000 00000 01000 00000 00000 00--0 -0000 00000 00000 00000 00000 00000 -0000 -0-0-
sec=1.739583 01- 04-02 0-000 00000 00000 00000 0--00 00000 01000 00000 0--00 00-00 --000 00000 00000 00000 -0-00 00000 0-000 00-00
sec=1.744792 01- 03-02 00000 00000 00000 0000- 00000 -0000 01010 -000- 00000 0-000 -0000 0000- 00--0 0--00 -0000 0000- 0000- -----
sec=1.750000 01- 04-02 00000 00000 00000 00000 -0000 00000 00010 00000 00000 00000 00-00 00000 00000 00000 00000 00--0 --000 --0-0
sec=1.755208 01- 03002 00000 00000 00000 00000 00000 00000 01000 0000- 00000 00000 -0000 00000 000-0 -000- 0--00 000-0 0-00- -----
sec=1.760417 01- 03002 00000 00000 00000 0-000 -0000 00000 00000 00-00 00000 00000 00000 00000 ----- ---00 0-000 000-0 -00-- -----
sec=1.765625 002 00300 10000 00000 00000 00000 000-0 00000 00000 0-00- 00000 0-000 -00-0 00000 --0-- ----0 0-00- 000-0 -0000 -0---
sec=1.770833 01- 03002 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 -0-00 -00-0 000-- 0000- 0---- 0-0-0
sec=1.776042 002 00300 10000 000-0 00000 00000 000-0 00000 00000 000-0 00000 000-- --000 00000 -0000 0-0-- 000-- 0000- 00-0- -----
sec=1.781250 010 03002 00000 00000 00000 00000 00000 00000 00000 0-000 00000 -0-00 ---00 00000 000-- 0---0 00000 00000 -0-0- -0-0-
sec=1.786458 002 00300 10000 00000 00000 0-000 000-0 00000 00000 00000 00000 000-- --0-0 00000 000-0 --00- --000 000-- ----0 -----
sec=1.791667 002 00200 1000- 00000 00000 00000 -0000 00000 00000 0-000 00000 0--00 --0-0 0000- 00--0 0-0-0 0-000 0000- ---0- ---0-
sec=1.796875 002 00300 10000 00000 00000 00000 000-- -00-0 00000 00000 00000 000-- 00-00 00000 00000 000-0 0--00 0000- -00-0 -----
sec=1.802083 001 00300 10000 00000 00000 0000- 0000- 00000 00000 0000- 00000 00-00 -0000 00000 0---- -00-- --000 0000- 0---- -----
sec=1.807292 001 00300 10000 00000 00000 00000 0-000 00000 00000 00000 0---- -0000 00000 0000- ----- --00- 0--00 000-- 0---- -----
sec=1.812500 001 00200 100-0 00000 00000 00000 -000- 000-0 00000 0000- -0000 0-00- 000-0 00000 ----- --00- --000 000-- -00-0 -----
sec=1.817708 001 00200 10000 00000 00000 00000 00000 00000 00000 00000 00000 00000 000-0 0000- ----- --000 -0000 000-- 0-00- 0----
sec=1.822917 010 02001 00000 00000 00000 00000 00-00 00-00 00000 0---0 0--00 00000 00--0 0000- 000-0 00000 -000- 0000- 0000- -----
sec=1.828125 010 02001 00000 00000 00000 0-000 0-000 0-000 00000 0-000 -0000 00--- ----0 00000 000-- 0--00 00000 00000 00--- -0---
sec=1.833333 010 02001 00000 00000 00000 0-0-- 0-0-0 0--00 00000 00--- 00-00 00000 0-0-0 0000- -0--- -000- --000 0000- 00--- -----
sec=1.838542 000 10010 00000 00000 00000 00000 00000 -0000 00000 -0000 00000 00--0 --0-- 00000 -00-- --00- --000 0000- 0000- -0---
sec=1.843750 000 02001 00000 00000 00000 000-0 000-- 00000 00000 00000 00000 00000 00--0 00000 --000 ---00 ---00 00000 ---00 ----0
sec=1.848958 000 01001 00000 00000 00000 00000 0-0-- 00000 00000 00000 --000 00--- -00-0 0000- --0-- --000 0---0 0000- -00-- -----
sec=1.854167 000 01001 00000 00000 00000 0--00 00000 0-000 00000 00--0 00000 00000 0---0 00000 ----- --000 ----0 0000- --00- -----
sec=1.859375 000 01001 00000 00000 00000 000-- 000-- 000-0 00000 000-0 00000 0-000 --0-- 0000- ----- --000 ---00 0000- ----- -----
sec=1.864583 000 01001 00000 00000 00000 00000 00000 0-000 00000 --000 00000 0000- -0--0 0000- ----- --000 ----0 00000 -00-- -----
  :

表示してる周波数の位置は -show_note_cen の指定なしなので、
ノート番号全体の中央64の辺り...

wnの値を確認してみます。
今回追加した -pitch_show オプションで実行。

$ cat jrt2-sawari.raw | midi_prog/tool -c 1 -adj_cent 50 -div 5 -pitch_show -shadow_cut_en 1 -pd_lmt_sec 0.0625
0 freq=8.223161 wn=1 sb.wsec=0.060804
1 freq=8.318709 wn=1 sb.wsec=0.060105
2 freq=8.415368 wn=1 sb.wsec=0.059415
3 freq=8.513150 wn=1 sb.wsec=0.058733
4 freq=8.612068 wn=1 sb.wsec=0.058058
5 freq=8.712135 wn=1 sb.wsec=0.057391
6 freq=8.813366 wn=1 sb.wsec=0.056732
7 freq=8.915772 wn=1 sb.wsec=0.056080
8 freq=9.019368 wn=1 sb.wsec=0.055436
9 freq=9.124168 wn=1 sb.wsec=0.054800
  :
56 freq=15.703632 wn=1 sb.wsec=0.031840
57 freq=15.886100 wn=1 sb.wsec=0.031474
58 freq=16.070687 wn=2 sb.wsec=0.062225
59 freq=16.257420 wn=2 sb.wsec=0.061510
  :
91 freq=23.528863 wn=2 sb.wsec=0.042501
92 freq=23.802255 wn=2 sb.wsec=0.042013
93 freq=24.078824 wn=3 sb.wsec=0.062295
94 freq=24.358607 wn=3 sb.wsec=0.061580
  :
116 freq=31.407264 wn=3 sb.wsec=0.047760
117 freq=31.772199 wn=3 sb.wsec=0.047211
118 freq=32.141374 wn=4 sb.wsec=0.062225
119 freq=32.514839 wn=4 sb.wsec=0.061510
  :
135 freq=39.116166 wn=4 sb.wsec=0.051130
136 freq=39.570673 wn=4 sb.wsec=0.050542
137 freq=40.030463 wn=5 sb.wsec=0.062452
138 freq=40.495594 wn=5 sb.wsec=0.061735
  :
151 freq=47.057726 wn=5 sb.wsec=0.053126
152 freq=47.604511 wn=5 sb.wsec=0.052516
153 freq=48.157649 wn=6 sb.wsec=0.062295
154 freq=48.717214 wn=6 sb.wsec=0.061580
  :
165 freq=55.318612 wn=6 sb.wsec=0.054231
166 freq=55.961383 wn=6 sb.wsec=0.053608
167 freq=56.611623 wn=7 sb.wsec=0.061825
168 freq=57.269418 wn=7 sb.wsec=0.061115
  :
176 freq=62.814529 wn=7 sb.wsec=0.055720
177 freq=63.544398 wn=7 sb.wsec=0.055080
178 freq=64.282749 wn=8 sb.wsec=0.062225
179 freq=65.029678 wn=8 sb.wsec=0.061510
  :
186 freq=70.506925 wn=8 sb.wsec=0.056732
187 freq=71.326176 wn=8 sb.wsec=0.056080
188 freq=72.154946 wn=9 sb.wsec=0.062366
189 freq=72.993346 wn=9 sb.wsec=0.061649
190 freq=73.841488 wn=9 sb.wsec=0.060941
  :
195 freq=78.232331 wn=9 sb.wsec=0.057521
196 freq=79.141347 wn=9 sb.wsec=0.056860
197 freq=80.060925 wn=10 sb.wsec=0.062452
198 freq=80.991188 wn=10 sb.wsec=0.061735
  :
204 freq=86.804206 wn=10 sb.wsec=0.057601
205 freq=87.812822 wn=10 sb.wsec=0.056939
206 freq=88.833158 wn=11 sb.wsec=0.061914
207 freq=89.865350 wn=11 sb.wsec=0.061203
  :
211 freq=94.115453 wn=11 sb.wsec=0.058439
212 freq=95.209022 wn=11 sb.wsec=0.057768
213 freq=96.315297 wn=12 sb.wsec=0.062295
214 freq=97.434427 wn=12 sb.wsec=0.061580
  :
218 freq=102.042503 wn=12 sb.wsec=0.058799
219 freq=103.228180 wn=12 sb.wsec=0.058124
220 freq=104.427633 wn=13 sb.wsec=0.062244
221 freq=105.641024 wn=13 sb.wsec=0.061529
  :
226 freq=111.922766 wn=13 sb.wsec=0.058076
227 freq=113.223246 wn=14 sb.wsec=0.061825
  :
232 freq=119.955851 wn=14 sb.wsec=0.058355
233 freq=121.349670 wn=15 sb.wsec=0.061805
  :
237 freq=127.088797 wn=15 sb.wsec=0.059014
238 freq=128.565497 wn=16 sb.wsec=0.062225
  :
242 freq=134.645890 wn=16 sb.wsec=0.059415
243 freq=136.210400 wn=17 sb.wsec=0.062403
  :
247 freq=142.652351 wn=17 sb.wsec=0.059585
248 freq=144.309891 wn=18 sb.wsec=0.062366
  :
252 freq=151.134901 wn=18 sb.wsec=0.059549
253 freq=152.891004 wn=19 sb.wsec=0.062136
  :
256 freq=158.282694 wn=19 sb.wsec=0.060019
257 freq=160.121850 wn=20 sb.wsec=0.062452
  :
261 freq=167.694673 wn=20 sb.wsec=0.059632
262 freq=169.643191 wn=21 sb.wsec=0.061895
  :
265 freq=175.625645 wn=21 sb.wsec=0.059786
266 freq=177.666317 wn=22 sb.wsec=0.061914
267 freq=179.730700 wn=22 sb.wsec=0.061203
268 freq=181.819070 wn=22 sb.wsec=0.060500
269 freq=183.931706 wn=22 sb.wsec=0.059805
270 freq=186.068889 wn=23 sb.wsec=0.061805
271 freq=188.230906 wn=23 sb.wsec=0.061095
272 freq=190.418043 wn=23 sb.wsec=0.060393
273 freq=192.630595 wn=24 sb.wsec=0.062295
274 freq=194.868854 wn=24 sb.wsec=0.061580
275 freq=197.133121 wn=24 sb.wsec=0.060873
276 freq=199.423698 wn=24 sb.wsec=0.060173
277 freq=201.740890 wn=25 sb.wsec=0.061961
278 freq=204.085006 wn=25 sb.wsec=0.061249
279 freq=206.456359 wn=25 sb.wsec=0.060545
280 freq=208.855267 wn=26 sb.wsec=0.062244
281 freq=211.282048 wn=26 sb.wsec=0.061529
282 freq=213.737027 wn=26 sb.wsec=0.060822
283 freq=216.220532 wn=27 sb.wsec=0.062436
284 freq=218.732893 wn=27 sb.wsec=0.061719
285 freq=221.274447 wn=27 sb.wsec=0.061010
286 freq=223.845532 wn=27 sb.wsec=0.060309
287 freq=226.446492 wn=28 sb.wsec=0.061825
288 freq=229.077674 wn=28 sb.wsec=0.061115
289 freq=231.739428 wn=28 sb.wsec=0.060413
290 freq=234.432110 wn=29 sb.wsec=0.061852
291 freq=237.156080 wn=29 sb.wsec=0.061141
292 freq=239.911701 wn=29 sb.wsec=0.060439
293 freq=242.699341 wn=30 sb.wsec=0.061805
294 freq=245.519371 wn=30 sb.wsec=0.061095
295 freq=248.372169 wn=31 sb.wsec=0.062406
296 freq=251.258115 wn=31 sb.wsec=0.061690
297 freq=254.177593 wn=31 sb.wsec=0.060981
298 freq=257.130995 wn=32 sb.wsec=0.062225
299 freq=260.118713 wn=32 sb.wsec=0.061510
300 freq=263.141147 wn=32 sb.wsec=0.060804
301 freq=266.198700 wn=33 sb.wsec=0.061984
302 freq=269.291780 wn=33 sb.wsec=0.061272
303 freq=272.420799 wn=34 sb.wsec=0.062403
304 freq=275.586176 wn=34 sb.wsec=0.061687
305 freq=278.788334 wn=34 sb.wsec=0.060978
306 freq=282.027698 wn=35 sb.wsec=0.062051
307 freq=285.304702 wn=35 sb.wsec=0.061338
308 freq=288.619783 wn=36 sb.wsec=0.062366
309 freq=291.973383 wn=36 sb.wsec=0.061649
310 freq=295.365951 wn=36 sb.wsec=0.060941
311 freq=298.797938 wn=37 sb.wsec=0.061915
312 freq=302.269802 wn=37 sb.wsec=0.061204
313 freq=305.782008 wn=38 sb.wsec=0.062136
314 freq=309.335024 wn=38 sb.wsec=0.061422
315 freq=312.929324 wn=39 sb.wsec=0.062314
316 freq=316.565388 wn=39 sb.wsec=0.061599
317 freq=320.243700 wn=40 sb.wsec=0.062452
318 freq=323.964753 wn=40 sb.wsec=0.061735
319 freq=327.729042 wn=40 sb.wsec=0.061026
320 freq=331.537070 wn=41 sb.wsec=0.061833
321 freq=335.389345 wn=41 sb.wsec=0.061123
322 freq=339.286382 wn=42 sb.wsec=0.061895
323 freq=343.228699 wn=42 sb.wsec=0.061184
324 freq=347.216825 wn=43 sb.wsec=0.061921
325 freq=351.251290 wn=43 sb.wsec=0.061210
326 freq=355.332633 wn=44 sb.wsec=0.061914
327 freq=359.461400 wn=44 sb.wsec=0.061203
328 freq=363.638140 wn=45 sb.wsec=0.061875
329 freq=367.863412 wn=45 sb.wsec=0.061164
330 freq=372.137779 wn=46 sb.wsec=0.061805
  :
377 freq=640.487400 wn=80 sb.wsec=0.062452
378 freq=647.929506 wn=80 sb.wsec=0.061735
379 freq=655.458084 wn=81 sb.wsec=0.061789
380 freq=663.074140 wn=82 sb.wsec=0.061833
381 freq=670.778690 wn=83 sb.wsec=0.061868
382 freq=678.572763 wn=84 sb.wsec=0.061895
383 freq=686.457399 wn=85 sb.wsec=0.061912
384 freq=694.433650 wn=86 sb.wsec=0.061921
385 freq=702.502580 wn=86 sb.wsec=0.061210
386 freq=710.665267 wn=86 sb.wsec=0.060507
  :
638 freq=13061.440367 wn=86 sb.wsec=0.003292
639 freq=13213.206993 wn=86 sb.wsec=0.003254


制限なしの値は 86 で、最も低い側は 1 まで制限されてます。
ノート番号0の周波数は確か

440 * e( (0-69)/12 * l(2) ) = 8.17579891564370733040
8 Hz ちょい

半周期1/(2*8)で 0.0625
なぜだか32分音符と想定して設定したlmt_secと同じくらいになってます。

そしてスペクトルを表示したあたりのノート番号64あたり
div=5なので64*5=320

317 freq=320.243700 wn=40 sb.wsec=0.062452
318 freq=323.964753 wn=40 sb.wsec=0.061735
319 freq=327.729042 wn=40 sb.wsec=0.061026
320 freq=331.537070 wn=41 sb.wsec=0.061833
321 freq=335.389345 wn=41 sb.wsec=0.061123
322 freq=339.286382 wn=42 sb.wsec=0.061895
323 freq=343.228699 wn=42 sb.wsec=0.061184

制限なしのwn=86からは、半分くらいのwn=41まで狭まってます。

先読み分が半分に減って「つっこみ」の程度が弱まり、
スペクトルの山は時間的に後にずれてます。

とりあえず波形を生成してみて、実際に聴いて確認してみる事にします。

小さな音を多めに拾っても、ベロシティで抑えられるので、あまり邪魔にはならないと踏んで、 閾値は最大の5%から1%までさげてみます。

jrt2-midi.sh
  :
-THRES=0.05
+THRES=0.01
  :

では、長〜い時間をかけて音符を抽出してMIDIデータを作らせて、そのMIDIデータから波形を生成。

$ ./jrt2-midi.sh jrt2-mono.raw
  :

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

開始冒頭にすごいノイズのように音符の嵐!

チャタ除去でかなり抑えられてはいますが...
ひょっとして...
abufを長くとりたいので、共通範囲の先頭の時刻で処理するように変更したから、 まだ全体の最大値がとれてない状態で、全ての鍵盤がバタバタしているのかも?

波形生成側も、同時に扱えるnoteバッファの上限を超えたメッセージが表示されて、悲鳴をあげてます。

とはいえ、冒頭のノイズ以降では、わりとまともに再現できてる様子。

低音側も、スペクトル表示での乱れほども、ぼやけて聴こえてない様子。

肝心の「つっこみ」の改善も効果が出てるようです。

thres を 1% まで下げた事で、今まではねられてたボコーダのような「うなり声」のパートも拾えてます。

そして、末尾のナレーションの再現。(^_^;


ナレーションの再現と言えば

プログラムが末尾のナレーションまで再現しようと頑張ってるのを聞いて、 ボコーダー を思い出しました。

そう言えば、恐れ多くも山下達郎大先生のDJのオープニングのセリフを「いけにえ」にして、ボコーダーを試してました。
voco.mp3
jet.mp3

これと同じ入力で音符を抽出を試してみます。

tool11.patch
jrt2-midi.sh-4.patch

ついでに rawmix2 を更新して、 複数の入力をL,Rに指定の-panで割り振って、指定の-volで音量を調節出来るように、更新してみました。

$ cat tool11.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-4.patch | patch -p0


ボコーダーの時の結果を再現しておいて

$ cat voice.raw | midi_prog/voco -c 1 -voice - > voco.raw
$ cat voice.raw | midi_prog/voco -c 1 -voice - -lpf_freq 100 -vco_f 40 -tune 3 > jet.raw


音符抽出の入力として同じファイルを指定

$ ./jrt2-mini.sh voice.raw
$ mv tmp_jrt2/jrt2.raw a.raw


左、中央、右と並べて混ぜてみます。

$ midi_prog/rawmix2 -CH0 "-i voco.raw -c 1 -pan 0" \
-CH1 "-i jet.raw -c 1 -pan 1 -vol 1.5" \
-CH2 "-i a.raw -c 1 -pan 0.5 -vol 0.7" > a_mix.raw


$ cat a_mix.raw | sox -t raw -r 44100 -c 2 -b 16 -s - a_mix.wav
$ lame a_mix.wav a_mix.mp3

a_mix.mp3

ボコーダーと比べると、キラキラ感が凄いです。


倍音を除去してみる

さて、モノマネ的な再現はちょっと置いといて...
「音符」抽出という本題に。

ここまでは、フーリエ変換的な処理で倍音を含めて抽出して、 倍音の位置の鍵盤も操作するMIDIデータを作って、 波形生成側で、SIN波で鳴らしてきました。

いわば波形生成側で、フーリエ逆変換してるようなものなので、 そりゃ〜音色の再現もされるでしょうし、ドラムも聞こえるでしょう。

という事で、本質に戻って倍音の除去を試してみます。

とりあえず最初はすごくシンプルな方式で試してみます。
ノートON/OFFを出力する前に、その音が誰かの「倍音」かどうか判定してみて、 倍音ならノートON/OFFを出力しないようにしてみました。

自分が誰かの「倍音」かどうかの判定も極めて単純な方式です。

説明するとややこしそうですが、プログラム的には簡単です。

+int
+note_onoff_is_overtone(struct note_onoff *nto, double on_sec, double off_sec, int velo)
+{
+	/* Fundamental, Overtone */
+
+	int lst[] = { 12, 12+7, 12*2, 12*2+4, 12*2+7 };
+	int lst_n = ARR_N(lst);
+	int i, t;
+
+	for(i=0; i<lst_n; i++){
+		struct note_onoff *funda;
+		int velo_f;
+
+		if((t = nto->note - lst[i]) < 0) continue;
+		funda = &note_onoff[t]; /* ! */
+		velo_f = note_onoff_velo(funda, on_sec, off_sec);
+
+		if(velo_f >= velo * 0.7) return 1;
+	}
+	return 0;
 }

  :

+	if(nto->cut_over_tone && note_onoff_is_overtone(nto, on_sec, off_sec, velo)) return;

ついでにいくつか更新してます。

一つはOFF時のタイミングの補正について。

使ってるSINの音のエンベロープのリリース期間を考えると、 スペクトルの強さが弱まり鍵盤OFFと判定してから、 エンベロープのリリースが始まります。

オリジナルの音が消えても、再現側だけリリースの音が残る事になり、 常にぼやけた印象で再現されてます。

この対策用として、コマンド引数で -pre_release で時間(秒)を指定して、 その分だけ鍵盤OFFイベントの時刻を早めるようにしてみました。

+	if(nto->pre_release > 0){
+		int off_lmt = on_sec + nto->ct.lmt_sec;
+		off_sec -= nto->pre_release;
+		if(off_sec < off_lmt) off_sec = off_lmt;
+	}

初期化の引数を変更しててバグに気がつきました。

  :

-note_onoff_init(struct note_onoff *nto, struct pitdet *pd, int note, int lmt_sec)
+note_onoff_init(struct note_onoff *nto, struct pitdet *pd, int note, int ac, char **av)
 {
+	double lmt_sec = opt_double("-lmt_sec", ac, av, 2.0/32);

  :

 	int abuf_wsec_chk = opt_idx("-abuf_wsec_chk", ac, av) > 0;
-	double lmt_sec = opt_double("-lmt_sec", ac, av, 2.0/32);
 	int sec, s;
  :
-	for(i=0; i<NOTE_N; i++) note_onoff_init(&note_onoff[i], &pd, i, lmt_sec);
+	for(i=0; i<NOTE_N; i++) note_onoff_init(&note_onoff[i], &pd, i, ac, av);
  :

-lmt_sec 指定の値はデフォルト値を含めて、doubleで保持出来てたはずですが、 note_onoff_init() 関数の引数を int にしてました。(>_<)
なので、int にキャストされて常に 0 で受け取り、チャタリング除去へとパス...
これまで実は、チャタリング除去が機能してなかったのでは?

tool12.patch
jrt2-midi.sh-5.patch

とりあえず、倍音除去以前に、チャタリング除去の確認を。

$ cat tool12.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-5.patch | patch -p0

$ ./jrt2-midi.sh
  :

$ head tmp_jrt2/abs
abs=00000016 on  ch=0 note=57 velo=8
abs=00000030 off ch=0 note=57 velo=8
abs=00000021 on  ch=0 note=89 velo=13
abs=00000050 off ch=0 note=89 velo=13
abs=00000023 on  ch=0 note=80 velo=5
abs=00000062 off ch=0 note=80 velo=5
abs=00000018 on  ch=0 note=84 velo=5
abs=00000079 off ch=0 note=84 velo=5
abs=00000022 on  ch=0 note=64 velo=1
abs=00000096 off ch=0 note=64 velo=1

なんか冒頭のノイズは消えてる様子。


$ cat tmp_jrt2/mix.raw | play -t raw -r 44100 -b 16 -c 2 -s -

あらー
「つっこみ」激しく、何か違う...

mix-sawari-ck1.mp3

あえてチャタリング除去を 0 に設定してみると

$ sed -e 's/\(^TOOL_OPT.*\)"/\1 -lmt_sec 0"/' jrt2-midi.sh > tst.sh
$ diff -u jrt2-midi.sh tst.sh
  :
-TOOL_OPT="-c 1 -adj_cent $ADJ_CENT -div $DIV -midi -thres $THRES -pd_lmt_sec 0.125"
+TOOL_OPT="-c 1 -adj_cent $ADJ_CENT -div $DIV -midi -thres $THRES -pd_lmt_sec 0.125 -lmt_sec 0"
  :

$ chmod a+x tst.sh
$ mv tmp_jrt2 tmp_jrt2-
$ ./tst.sh
  :

$ head tmp_jrt2/abs
abs=00000000 on  ch=0 note=98 velo=9
abs=00000002 off ch=0 note=98 velo=9
abs=00000000 on  ch=0 note=99 velo=9
abs=00000002 off ch=0 note=99 velo=9
abs=00000001 on  ch=0 note=31 velo=3
abs=00000003 off ch=0 note=31 velo=3
abs=00000001 on  ch=0 note=37 velo=3
abs=00000003 off ch=0 note=37 velo=3
abs=00000000 on  ch=0 note=44 velo=11
abs=00000003 off ch=0 note=44 velo=11

時刻が開始直後すぎて、ON/OFF期間も短かすぎて


$ cat tmp_jrt2/mix.raw | play -t raw -r 44100 -b 16 -c 2 -s -

再生してみると、これまでの感じ...
ということは、これまでやっぱりチャタリング除去できてませんでした。(>_<)

mix-sawari-ck2.mp3

デフォルトの設定 2.0/32 sec でチャタ除去すると「つっこみ」が激しくなってる...?

なんだか倍音除去どころじゃなくなってきました。
チャタリング除去に何か問題があるのでは?
util.c に移したコードを点検してみると...

int
chatt_add(struct chatt *ct, double sec, int onoff)
{
	if(onoff == ct->onoff){
		ct->chg_try_sec = -1;
		return 0;
	}
	if(sec - ct->sec < ct->lmt_sec) return 0; // ここ!!

	if(ct->chg_try_sec < 0){
		ct->chg_try_sec = sec;
		return 0;
	}
	if(sec - ct->chg_try_sec < ct->lmt_sec) return 0;	

	ct->sec = ct->chg_try_sec;
	ct->onoff = onoff;
	ct->chg_try_sec = -1;
	return 1;
}

「ここ」の行が不要かと。
記録開始がlmt_sec遅れてたので、2倍の設定にした扱いになってたはずです。
でも、この修正自体は本質的な不具合では無いはず...

コードを見直してみると、他の関係なさそうな箇所で、また間違い発見(>_<)

tool.c

struct note_onoff{
	struct chatt ct;
	struct abuf ab;
	struct pitdet *pd;
	int note;
	double on_sec;
	int pre_release; // ここ!!
	int cut_over_tone;
} note_onoff[ NOTE_N ];

「ここ」はintでなくてdoubleでした。

void
note_onoff_add(struct note_onoff *nto, double sec, int onoff, double v, int ch)
{
  :
	if(nto->pre_release > 0){
		int off_lmt = on_sec + nto->ct.lmt_sec; // ここ!!
  :

「ここ」もまたintでなくてdoubleでした。

次回のパッチで反映するとして...
チャタリング除去をすると、なぜに?

設定を戻しつつ色々と試してみると、どうやら -thres の閾値の指定が影響してる様子。
-thres 0.05 の 5%の設定に戻してみると、はげしい「つっこみ」は消えました。

-thres 0.001 にして 0.1%まで下げると、バックのコーラス的なパートも拾えてて、 いい感じだったはずなのですが、そのあたりの倍音が主旋律に先行して、 主旋律と同じノートを先にONにしていたのかも知れません...


倍音を除去してみる (その2)

前回のバグを修正して、今度こそ倍音除去を試しみます。

tool13.patch
jrt2-midi.sh-6.patch
tone2.patch

気分を変えて、音色をSIN波から適当に変更してみました。
ただしattach と release は、超短めの設定で。

そして時短対策。
pitdetのバイナリデータをファイルにセーブしておいて、次回の起動でファイルがあればロードするようにしてみました。

$ cat tool13.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-6.patch tone2.patch | patch -p0

まず倍音除去なしで
jrt2-mini.sh は倍音除去あり設定にしてあるので、無し版を tst.sh に

$ sed -e 's/^OVT=.*$/OVT=/' jrt2-midi.sh > tst.sh
$ chmod a+x tst.sh
$ ./tst.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

$ mv tmp_jrt2 tmp_jrt2-

そして、倍音除去ありで

$ ./jrt2-midi.sh jrt2-mono.raw

tst.sh 実行時に生成した pd-4 というバイナリファイルからデータを読み出すので、
Pickup の処理がかなり速くなりました。

と言っても、-div 5 で生成したpd-4 のサイズはCDROM一枚分くらいのオーダー...

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

やはり、倍音除去の処理が乱暴すぎました。
主旋律の一部が、短く強く鳴ってる低音のパートの倍音と判定されて除去されたりで、 なかなか芸術的な味のある演奏になってしまってます。


倍音を除去してみる (その3)

乱暴過ぎる除去処理を反省しつつ、そもそも倍音のレベルとは、基音のどのくらいでしょうか?

$ midi_prog/tool -adj_cent 50 -div 5 -pd_load pd-4 -show -show_w 130 | less

  :
sec=3.989583 --- --000 0---- ----- ----- ----- ----- ----- 0000- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----
sec=3.994792 --- -00-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----
sec=4.000000 --- 00-0- ----- 0---- ----- ----- ----- ----- 000-- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----
sec=4.005208 000 00-0- --0-- 0---- 0--0- -0--- ----- -0-0- 0000- --000 -000- 00000 00000 00000 00000 0--00 000-- 00--0 000-0 0----
sec=4.010417 111 00000 00000 00000 00000 ----- ----- -0--- ----- --000 00000 00000 0000- --0-- ----- ----- ----- ----- ----- -----
sec=4.015625 -00 00000 00111 11111 11110 00000 00000 0---- ----- --000 00111 00000 00--- ----- ----- ----- ----- ----- ----- -----
sec=4.020833 --0 00000 11122 11111 11110 00000 -0--- ----- ----- ----- --000 11111 100-- ----- ----- ----0 000-- ----- ----- -----
sec=4.026042 --- ----0 00112 22222 22111 0000- ----- ----- ----- ----- -0011 11111 00--- ----- ----- ----- ----- ----- ----- -----
sec=4.031250 --- ----- ---00 11223 33322 1100- ----- ----- ----- ----- --011 21111 0---- ----- ----- ----- 0000- ----- ----- -----
sec=4.036458 000 ----- ---01 12333 33322 10--- ----- ----- ----- ----- ---01 12221 0---- ----- ----- ----- 0000- ----- ----- -----
sec=4.041667 --- 0000- ----- 12334 44432 10--- ----- ----- ----- ----- ---01 22221 0---- ----- ----- ----- -000- ----- ----- -----
sec=4.046875 --- ----- ----- -1234 55543 20--- ----- ----- ----- ----- ---01 23220 ----- ----- ----- ----- 0000- ----- ----- -----
sec=4.052083 --- ----0 0---- -1235 55543 1---- ----- ----- ----- ----- ----1 23320 ----- ----- ----- ----- 0000- ----- ----- -----
sec=4.057292 --- ----- -0--- -0235 56542 1---- ----- ----- ----- ----- ----1 23320 ----- ----- ----- ----- 0000- ----- ----- -----
sec=4.062500 --- ----- ----- --124 56643 1---- ----- ----- ----- ----- ----0 23320 ----- ----- ----- ----- 000-- ----- ----- -----
sec=4.067708 --- ----- ----- --124 56543 1---- ----- ----- ----- ----- ----0 12220 ----- ----- ----- ----- 000-- ----- ----- -----
sec=4.072917 --- ----- ----- --024 56542 0---- ----- ----- ----- ----- ----0 12220 ----- ----- ----- ----- -000- ----- ----- -----
sec=4.078125 --- ----- ----- --013 45553 1---- ----- ----- ----- ----- ----0 22210 ----- ----- ----- ----- -00-- ----- ----- -----
sec=4.083333 --- ----- ----- --023 55543 1---- ----- ----- ----- ----- ----0 12220 ----- ----- ----- ----- 000-- ----- ----- -----
sec=4.088542 --- ----- ---0- --024 55542 0---- ----- ----- ----- ----- ----0 12210 ----- ----- ----- ----- -00-- ----- ----- -----
sec=4.093750 --- ----- ----- --013 45543 1---- ----- ----- ----- ----- -00-0 12210 ----- ----- ----- ----- -000- ----- ----- -----
sec=4.098958 --- ----- ----- --023 45543 1---- ----- ----- ----- ----- ----0 12220 ----- ----- ----- ----- -00-- ----- ----- -----
sec=4.104167 --- --0-- ----- --023 45532 0---- ----- ----- ----- ----- ----0 12210 ----- ----- ----- ----- -000- ----- ----- -----
sec=4.109375 --- ----- ----- --012 44443 1---- ----- ----- ----- ----- ----0 12210 ----- ----- ----- ----- -000- ----- ----- -----
sec=4.114583 --- ----- ----- --013 44432 1---- ----- ----- ----- ----- ----0 11210 ----- ----- ----- ----- -000- ----- ----- -----
sec=4.119792 --- ----- ----- --023 44432 0---- ----- ----- ----- ----- ----0 12210 ----- ----- ----- ----- -000- ----- ----- -----
  :

真中がノート番号64あたりで、いかにも3つの倍音が出てて、この3つの山のさらに低い側に基音がある風です。

sec=4.072917 --- ----- ----- --024 56542 0---- ----- ----- ----- ----- ----0 12220 ----- ----- ----- ----- -000- ----- ----- -----

この時刻のデータを詳細に見てみます。
-show_detail_sec なるオプションを追加してみます。
(patch は、後程まとめます)

$ midi_prog/tool -adj_cent 50 -div 5 -pd_load pd-4 -show -show_w 130 -show_detail_sec 4.072917 | less
  :
0:-1.000000 1:0.000430 2:0.000767 3:0.001327 4:0.002106 | 5:0.002984 6:0.003757 7:0.004314 8:0.004487 9:0.004432 | 10:0.004069
11:0.003371 12:0.002569 13:0.001818 14:0.001220 | 15:0.000759 16:0.000412 17:0.000278 18:-1.000000 19:-1.000000 | 20:-1.000000
21:-1.000000 22:-1.000000 23:-1.000000 24:-1.000000 | 25:-1.000000 26:-1.000000 27:-1.000000 28:-1.000000 29:0.000330 |
30:0.000739 31:0.001374 32:0.002186 33:0.003125 34:0.003995 | 35:0.004861 36:0.005247 37:0.005555 38:0.005466 39:0.005311 |
40:0.004612 41:0.003791 42:0.002831 43:0.002043 44:0.001408 | 45:0.000955 46:0.000588 47:0.000325 48:-1.000000 49:-1.000000 |
50:-1.000000 51:-1.000000 52:-1.000000 53:-1.000000 54:-1.000000 | 55:-1.000000 56:-1.000000 57:-1.000000 58:0.001352
59:0.001674 | 60:0.002133 61:0.002681 62:0.003017 63:0.003199 64:0.003071 | 65:0.002339 66:0.001598 67:0.001105 68:0.000925
69:0.000996 | 70:0.001269 71:0.001709 72:0.002077 73:0.002427 74:0.002505 | 75:0.002231 76:0.001484 77:0.000869 78:0.000623
79:0.000481 | 80:0.000446 81:0.000519 82:0.000689 83:0.000942 84:0.001237 | 85:0.001573 86:0.001947 87:0.002541 88:0.003030
89:0.003508 | 90:0.003697 91:0.003604 92:0.003240 93:0.001015 94:0.000511 | 95:0.000329 96:-1.000000 97:-1.000000 98:-1.000000
99:-1.000000 | 100:-1.000000 101:-1.000000 102:-1.000000 103:-1.000000 104:-1.000000 | 105:-1.000000 106:-1.000000 107:-1.000000
108:-1.000000 109:-1.000000 | 110:-1.000000 111:-1.000000 112:-1.000000 113:-1.000000 114:0.000291 | 115:0.000559 116:0.000755
117:0.000780 118:-1.000000 119:-1.000000 | 120:0.000528 121:0.001356 122:0.001930 123:0.002098 124:0.001843 | 125:0.001255
126:0.000374 127:-1.000000 128:-1.000000 129:-1.000000 | 130:-1.000000 131:-1.000000 132:0.000403 133:0.000730 134:0.000815 |
  :

ノート番号64あたりのピークの位置を整理すると

  :
236(47):26850  ;;; 基音
296(59):16323  ;;; 2倍音
332(66):07760  ;;; 3倍音
357(71):01514  ;;; 4倍音

()の中は -div 5 なので、インデックスを5で割った値でノート番号に相当します

音色に因るでしょうが、3倍音までを対象にして、1/2, 1/3 くらい?
この倍数による「強さ」も倍音判定の基準に使ってみることにします。

色々と試してみると -thres を下げると途端に状況が悪化します。
-thres を下げた方が拾いきれなかったパートが拾えるようになり、よさげに思えますがさにあらず。
まず、十分拾えてた音のOFF判定がだらだらと先送りされてしまいます。
ピークのレベルから、かな〜り減衰してようやくOFF判定されるので、 全体のベロシティは平均で決めてるので、小さめの音が長〜く続く事になり、 「ぼやけた」再現になってしまいます。

OFF判定がくるまでに、次の山が登場してしまうと、OFFが入る事なくそのまま合体。
これまた長〜い1つのONになるはずです。

小さいレベルのパートも拾いたいし、大きいレベルのパートのOFF判定もちゃんとしたい。
さて、どうしたものか...

とりあえずコードの整理と、倍音のレベル判定だけ追加して、-thres を 5%から3%に下げてみました。

tool14.patch
jrt2-midi.sh-7.patch

$ cat tool14.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-7.patch | patch -p0

$ ./jrt2-midi.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

やはり -thres を 5% から 3%まで下げると、タイミングが乱れてきます。


倍音除去以前にON/OFF判定やりなおし

倍音除去の前に、ノートON/OFFイベントを出す判定に問題ありです。

まず、スクリプトの -cut_over_tone 指定はコメントアウトで、除去なし設定に戻しておきます。

そして、-thres の閾値を下げたときに、OFF判定が先に伸びてしまう対策を。

場当たり的ですが、この方式なら -peak_thres を 10% くらいにしておけば、だらだらリリースが伸びずにOFF判定できるかと。

従来の -thres は 3%で、-peak_thres は 10%で試してみます。

tool15.patch
jrt2-midi.sh-8.patch

$ cat tool15.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-8.patch | patch -p0

$ ./jrt2-midi.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

たしかに効果ありです。
-thres を 3% まで下げても、リリースがボヨーンと伸びてません。
あと、いくつかバグをとった影響で、低音の判定がしっかりしてきました。

ならば...
-thres 1% まで下げて、-peak_thres を 30% まで上げてみます。

$ sed -e 's/^THRES=0.03/THRES=0.01/' -e 's/^PEAK_THRES=0.1/PEAK_THRES=0.3/' jrt2-midi.sh | bash -s jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

カチっとOFF判定で音が切れていい感じですが...
第2楽章(?)からのドラムが入ると、主旋律のメロディーが分断されてます。

なるほど。
メロディーのON判定期間中に、ドラムのノイズが短く強くのっかると、そこで最大値がぐんと上がります。
-peak_thres 10% なら OFF判定にかかってなかったのに、30% まで上げるとひっかかって、一旦OFFにされてしまってます。

となると、ON期間中の最大値で判定するよりも、平均値で判定した方がいいのかも知れません。

それぞれのパートのON/OFFがここまでクリアになってきても、 相変わらずところどころ「つっこみ」が入ってきてます。

よくよく原因を考えてみたら、思いつきました。

「つっこみ」の箇所を注意して聴くと、上のオクターブへの移動とか5度上への移動とかの箇所っぽいです。
下の音OFFで残響が残ってるうちに、オクターブ上の音ON。
その時、実は下の音の2倍音が小さく鳴ってて、-thres を下げるとひそかにON判定されているとすると...
次のオクターブ上の音ONで、前の小さな2倍音と、後ろの大きな基音が合体。
合体した1つのON/OFFは、平均の音量で鳴るので「つっこみ」として現れるのではなかろうか。

オクターブ上
				   /-------------------------------\
				  /                                 \
				 /                                   \
				/                                     \
	 /---------------------\                                       \
	/                       \                                       \
  ----------------------------------------------------------------------------


下
       /---------------------------\
      /                             \
     /                               \
    /                                 \
   /                                   \
  /                                     \
  -----------------------------------------------------------------------------

上から下へ下がるときに「つっこみ」が入ってる箇所もあるので、 その場合は、該当しない気がするので、外してるかも知れませんが...

もしこの予想が正しければ、倍音除去こそが「つっこみ」対策にもなると。
でも、今の単純な倍音除去の処理では、上側の基音を含めたON/OFF全体が除去されるので、 そう簡単にはいきません。


倍音除去以前にON/OFF判定やりなおし (その2)

-thres の閾値を下げて、-peak_thres を上げるとドラムで、音がバシバシ分断される問題について

前回の「ON期間中の最大値で判定するよりも、平均値で判定した方がいいのかも知れません」
この考えそのままに、平均値に変更してみます。
-peak_thres は -mean_thres に変更です。

tool.c でしてた ON/OFF判定は、本質の部分だけを pitch.[ch] に移して整理しておきます。

tool16.patch
jrt2-midi.sh-9.patch

$ cat tool16.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-9.patch | patch -p0

まず -thres 3% の -mean_thres 10% の場合
$ ./jrt2-midi.sh jrt2-mono.raw

Pickup処理が重くなってます。
最大値の更新に比べて、バカ正直に毎回全体の平均を取り直してました...
次回に対策してみるとして...

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

最大値による判定の時と比べて、とくに変わった様子はない感じです。

そして -thres 1% の -mean_thres 30% の場合
$ sed -e 's/^THRES=0.03/THRES=0.01/' -e 's/^MEAN_THRES=0.1/MEAN_THRES=0.3/' jrt2-midi.sh | sh -s jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

ドラムで途切れないように聞こえますが、 最大値による判定の時と比べて、リリースの「ひっぱり」が伸びてて、「つっこみ」が激しくなってるかも。

さらに -thres 1% の -mean_thres 40% の場合
$ sed -e 's/^THRES=0.03/THRES=0.01/' -e 's/^MEAN_THRES=0.1/MEAN_THRES=0.4/' jrt2-midi.sh | sh -s jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

30% に比べて「ひっぱり」のぼやけがましになってますが、確かに「つっこみ」がきついです。


まともな倍音除去に備えて部品を追加

まず先に、バカ正直に毎回平均値を求めてしまってる箇所の改善から。

保持する値を合計値に変更して、処理が軽くなるよう変更。
これで、最大値で判定してた時と同じ程度の処理速度に戻りました。

そして、まともに倍音除去するために必要な部品を追加しておきます。

buf.[ch] に a2buf を追加。
abuf のWrapperというか何というか...

従来の abuf を2つポイントして「合成関数」的に値を返すようにしてます。
2つの関数について、2次式までの係数を予め指定して、新たな関数を合成します。
a2buf は abuf から継承してて、ポイントする2つの abuf は、a2buf も指定可能です。

APIのメモ

struct a2buf{
	struct abuf ab, *a, *b;
	struct tbuf *tb;

	/* A2 * a^2 + B2 * b^2 + AB * a*b + A * a + B * b + C */
	double A2, B2, AB, A, B, C;
};

// a2buf 構造体
//   先頭のab は abuf から継承のため
//     とはいえ、実は abuf に追加したメンバ type しか使ってません

//   a, b : ポイントする 2つの abuf
//          a2buf のアドレスでも可
//          どちらか1つでよければ、使わない方は NULL を指定する

//   tb   : sec と sridx の変換用に、底の a の tbuf をポイント
//          a が NULL なら b の tbuf をポイント

//   A2 ... C : 2次式までの係数


void a2buf_init(struct a2buf *a2b, struct abuf *a, struct abuf *b,
		double A2, double B2, double AB, double A, double B, double C);
//  初期化
//    引数は、ほぼそのまま a2b構造体へ


double a2buf_get(struct a2buf *a2b, double sec);
//  合成した関数のように結果を返す

//  例えば
//     abuf_init(a2b, a, NULL, 0,0,0,0,0,123);
//     abuf_get(a2b, sec) ならば
//     aのsecの時の値に123を足した結果を返す

//     abuf_init(a2b, a, b, 0,0,0,1,-1,0)
//     abuf_get(a2b, sec) ならば
//     aのsecの時の値からbのsecの時の値を引いた結果を返す

//     abuf_init(a2b, a, b, 1,0,0,0,1,0)
//     abuf_get(a2b, sec) ならば
//     aのsecの時の値を2乗したものに、bのsecの時の値を足した結果を返す


double a2buf_sum(struct a2buf *a2b, double from_sec, double to_sec, int *ret_n);
// 指定範囲の合計を返す
// ret_n が NULL で無ければ 底の tbuf で参照したデータの数を *ret_n に返す

double a2buf_mean(struct a2buf *a2b, double from_sec, double to_sec, int *ret_n);
// 指定範囲の平均を返す

double a2buf_stdev(struct a2buf *a2b, double from_sec, double to_sec, int *ret_n, double *ret_mean);
// 指定範囲のいわゆる「標準偏差」を返す
// ret_n が NULL で無ければ 底の tbuf で参照したデータの数を *ret_n に返す
// ret_mean が NULL で無ければ、処理過程で算出した指定範囲の平均も返す

double a2buf_correlation(struct a2buf *a2b_a, struct a2buf *a2b_b, double from_sec, double to_sec, int *ret_n);
// 指定範囲のいわゆる「相関」を返す
// -1 から 1 の値で、0 で相関なし
// 0.7 くらいあれば、指定範囲のデータの「形」が似てるはず
// 「形」が似てるだけで、「大小」のスケールは異なる

double a2buf_corr_k(struct a2buf *a2b_a, struct a2buf *a2b_b, double from_sec, double to_sec, int *ret_n);
// a2b_a と a2b_b に相関があるときに使用する
// 指定範囲について、a2b_b から k * a2b_a を引いた結果が、a2b_a との相関が無くなるような
// 倍率 k を求めて返す

// 例えばa2b_a が基音、a2b_bが倍音として、相関がある状態の場合
// このAPIで k を求めれば
// 基音の波形を k倍 した波形が、倍音に表れていると解釈できる
// 倍音から基音のk倍分だけを除去すればよさそう

// 倍音から除去した残りは、基音と相関の無い波形なので、
// 他の基音からの倍音であったり、自身が基音であったりするだろう

tool17.patch
jrt2-midi.sh-10.patch

未だ部品を用意しただけで、使ってはいません。

前回、最大値から平均値に変えて「ぼやけた」印象がありました。
最大値に対する10%でOFF判定してたのが、 平均値に対する10%でOFF判定になったので、 判定のレベルは下がるはずで、その分リリースが伸びるはず。
なるほど納得です。

-mean_thres を 10% から 30% にして試してみます。

$ cat tool17.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-10.patch | patch -p0

$ ./jrt2-midi.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

確かに30% まで上げると、OFF時の切れ味が増してる感が得られます。


まともな倍音除去をお試し

えい、やーっと、コーディングしてみました。
倍音側を削って、基音側に強さを移動させる試みを入れてますが、 まだ有効にせず、倍音側を削るのみにしてます。

各pitchのスペクトルのバッファで共通に有効な領域の、 最新側のOFF判定を使って、バッファ上で倍音を除去の処理、 最古側でノートON/OFF判定するように変更してます。

tool18.patch
jrt2-midi.sh-11.patch
o

$ cat tool18.patch | (cd midi_prog ; make clean ; patch -p1 ; make)
$ cat jrt2-midi.sh-11.patch | patch -p0

$ ./jrt2-midi.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

ノートON/OFF判定の位置を最古側に変更したので、 全体の最大値が先に程良く設定されるようになり、 頭のフェードインが、まともに再現できるようになりました。

急に、「ビー」とか「ブー」とか鳴ってます...

果たして、つっこみとしては、少しは効果出てるものなのだろうか?


急に、「ビー」とか「ブー」とか鳴る対策です。

影除去でOFF判定箇所のスペクトルの値を-1にしてた箇所が怪しかったのと、 倍音除去したあと、ON/OFF状態を再計算するときに、2つ保持してるチャタリング構造体のメンテが抜けてました。

tool19.patch

$ cat tool19.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

$ ./jrt2-midi.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

急に、「ビー」とか「ブー」とか鳴らなくなって、だいぶ、それらしくなってきました。


倍音除去後の倍音側の値が負に入ってる事もあろうかと

負なら0に引き上げる修正を入れてみたら、以外に効果があった様子です。

封印してた、除去分を基音へ足し込む処理を入れてみます。

さらに封印してた、処理対象を倍音としても扱って除去する処理も、 解放してみます。

ついでに、midtxt に -note_to_str と -str_to_note オプションを追加です。
生成したabsファイルについて

$ cat tmp_jrt2/abs | sort | midi_prog/midtxt -note_to_str | less

などとして確認するとき、より人にやさしい表示のため。

tool20.patch

$ cat tool20.patch | (cd midi_prog ; make clean ; patch -p1 ; make)

$ ./jrt2-midi.sh jrt2-mono.raw

mix_00.mp3
mix_01.mp3
mix_02.mp3
mix_03.mp3

低い側の音が、急に大きくなった印象です。


音色判別試行錯誤中

処理を見直してると、いくつかバグを発見。
そして、しれっと修正しておきます。

倍音除去はちょっと休憩。
除去する前の倍音の相関を手掛かりに、音色を判別できないか試行錯誤中です。 判別した音ごとにMIDIチャンネルを割り振ろうと試してますが、なかなか難航してます。

チャンネルごとに適当に音色を割り振って、左から順にPANで並べてみました。
が、結局ほとんどチャンネル0に集中して、ほぼ左端で鳴ってる有様です。

あまり上手くいかないままに、かなりソースに手を入れてるので、一旦更新しておきます。

tool21.patch
jrt2-midi.sh-12.patch
lk.sh

mix_aa.mp3
mix_ab.mp3
mix_ac.mp3
mix_ad.mp3


バッファの見てる位置が末尾過ぎてまずかったので、修正したものの...
音色の判別はうまくいかずでした。

ちょっと休憩。
MIDIチャンネルの音色を変えて、PANでノート番号順に並べてみました。

tool22.patch
jrt2-midi.sh-13.patch

mix_aa.mp3
mix_ab.mp3
mix_ac.mp3
mix_ad.mp3


さらに休憩。

以前打ち込みした ptmsk-14.mp3 を元に、生データの .raw に戻します。

$ lame --decode ptmsk-14.mp3 - | sox -t wav - -t raw ptmsk-14.raw

この波形のデータを聞かせてMIDIファイルを生成して、 もう一度波形を生成しなおしてみます。

ptmsk-midi.sh

$ ./ptmsk-midi.sh

ptmsk_midi.mp3

これはこれで、味がある。

$ cd tmp_ptmsk_midi
$ midi_prog/rawmix2 -CH0 "-i ptmsk_midi.raw -c 2 -pan -1" -CH1 "-i ../ptmsk-14.raw -c 2 -pan -1 -vol 0.5" \
| sox -t raw -r 44100 -c 2 -b 16 -s - ptmsk_mix.wav
$ lame ptmsk_mix.wav ptmsk_mix.mp3

ptmsk_mix.mp3

さらに、オリジナルを半分の音量で足し込んでみました。


script をまとめなおしてみました。

midi.sh
wav_mp3.sh
split.sh

$ ./midi.sh [ .rawファイルパス [ 名前 [ Mix時のオリジナルの音量の比率 [ progへの追加オプション ]]]]

  デフォルト値
  .rawファイルパス              : ptmsk-14.raw
  名前                          : ptmsk_midi
  Mix時のオリジナルの音量の比率 : 0.5
  progへの追加オプション        : なし

なので引数なしで実行すると、ptmsk-midi.sh と同等です

$ ./midi.sh

では、tmp_ptmsk_midi/ 以下に出力します。
ただし、ptmsk_midi.pd だけは、誤消去防止のためカレントディレクトリに出力します。

日曜日の昼下がりのFMラジオ。 敬愛する山下達郎さんの番組「サンデーソングブック」のオープニグ・テーマ曲でおなじみ、 'Only with you' を入力して試してみます。

only_with.mp3 などと用意しつつ

$ lame --decode only_with.mp3 - | sox -t wav - -t raw only_with.raw
などとして生データにして

$ ./midi.sh only_with.raw olwy_midi 0.05 -pure

olwy_midi_aa.mp3
olwy_midi_ab.mp3

Big wave 30th anniversary edition のボーナストラックの最後に入ってる'Only with you' で試してます。


音色の判別につまづいたままに、このテキトーな設定の再生も、これはこれで面白い気がします。
調子にのって、ハードロック・ギターな曲でも試してみます。

橘高文彦さんの確か「タッピング入門」的なタイトルのビデオに入ってた'Magic Touch'という曲で試してみます。

masic_org.mp3 などと用意しつつ

lameでうまくいかなかったので...
$ mpg123 -w masic_org.wav masic_org.mp3
$ sox masic_org.wav -t raw - > masic.raw

$ ./midi.sh masic.raw masic 0.5 -pure

masic.mp3
masic_mix.mp3

どうもチューニングが低いみたいなので修正。
typo の masic も magic に修正。

midi.sh-1.patch

$ cat midi.sh-1.patch | patch -p0

$ ln -s masic.pd magic.pd
$ env ADJ_CENT=25 ./midi.sh masic.raw magic 0.5 -pure

magic.mp3
magic_mix.mp3

もうちょい高めで。

$ ln -s masic.pd magic_28.pd
$ env ADJ_CENT=28 ./midi.sh masic.raw magic 0.5 -pure

magic_28.mp3
magic_mix_28.mp3

単体で聴くとリズムが不明でカオスな状態ですが、 オリジナルをミックスすると、がんばって超速弾きについていこうとしてる様子がうかがえます。


じゃあ次はアニメソング。

昔のアニメや声優に詳しい友人にコピーしてもらったMP3ファイルで試してみます。

ガンダムのマチルダさんがセリフ付きで歌ってます。

oyasumi_midi_aa.mp3
oyasumi_midi_ab.mp3
oyasumi_midi_mix_aa.mp3
oyasumi_midi_mix_ab.mp3

オリジナルをMIXしてない、MIDIファイルから波形生成しただけの単体の方でも、 歌おうとがんばってて、かすかに歌ってるように聞こえます。

譜面であるところの、このMIDIデータの通り、きっちりなタイミングで、きっちりな音量で楽器を演奏すれば、 このくらい歌ってるように聞こえるということでしょうか?

人間技では無理かもしれません。

どうも音色のピーコロ感からして、ガンダムというよりはスターウォーズの R2-D2が歌ってる感じです。

砂漠でマチルダさんに合わせてR2-D2が歌ってるような、不思議な印象です。


このところ更新できてないので、せめてもの追加です。

sparkle_midi_aa.mp3
sparkle_midi_ab.mp3
sparkle_midi_mix_aa.mp3
sparkle_midi_mix_ab.mp3

夏の曲、達郎先生のスパークル。
オリジナルのMIXは 0.1 で ;-p)


ダウンロードとビルドのまとめ 2018/Feb

あまりに長期間放置してましたが、久しぶりに手持ちの環境でビルドを試してみたところ、、、

かなり苦労するハメになったので記録しておきます。

手持ちMacな環境

$ uname -a
Darwin kondoumasao-no-MacBook-Air.local 15.6.0 Darwin Kernel Version 15.6.0: Tue Apr 11 16:00:51 PDT 2017; root:xnu-3248.60.11.5.3~1/RELEASE_X86_64 x86_64

ここまでのソースとパッチファイルをダウンロードしてきて、 格闘した挙句何とかビルドを通して、ptmsk-14.mp3 を生成できたので、スクリプトにしておきます。

midi_dl_build.sh
$ mkdir -p /tmp/work
$ cp midi_dl_build.sh /tmp/work/
$ cd /tmp/work
$ sh midi_dl_build.sh 
  :

$ ls -lt | head
total 5920
-rw-r--r--    1 kondoh  wheel  2870542  2 19 13:37 ptmsk-14.mp3
drwxr-xr-x   75 kondoh  wheel     2550  2 19 13:35 midi_prog
-rw-r--r--    1 kondoh  wheel      888  2 19 13:35 ptmsk-0_10.tone.patch
-rw-r--r--    1 kondoh  staff    13352  2 19 13:35 ptmsk-14.sh
-rw-r--r--    1 kondoh  staff    19928  2 19 13:35 ptmsk-14.tone
-rwxr-xr-x    1 kondoh  wheel   103612  2 19 13:35 cui_midi
drwxr-xr-x  107 kondoh  wheel     3638  2 19 13:35 cui
-rw-r--r--@   1 kondoh  wheel     6309  2 19 13:35 cui_midi.c
drwxr-xr-x    9 kondoh  wheel      306  2 19 13:35 kondoh.html.xdomain.jp

ちょっと追加変更しました。

midi_dl_build2.sh
$ mkdir -p /tmp/work
$ cp midi_dl_build2.sh /tmp/work/
$ cd /tmp/work
$ sh midi_dl_build2.sh 
patching file kbd.h
patching file kbd.c
patching file scpanel.c
patching file cui.c
patching file cui.h
patching file menu.c
patching file cui.c
patching file cui.h
patching file cui_test.c
patching file menu.c
patching file menu.h
patching file menu.c
patching file menu.h
patching file etext.c
patching file cui.c
patching file cui_srv.c
patching file cui_test.c
patching file term.c
patching file term.h
patching file panel.c
patching file panel.h
patching file scpanel.c
patching file ckbox.c
patching file ckbox.h
patching file scpanel.c
patching file etext.c
patching file etext.h
patching file focus.c
patching file num.c
patching file focus.c
patching file cui.c
patching file focus.c
patching file focus.h
patching file term.c
patching file term.c
gcc -Wall   -c -o cui.o cui.c
gcc -Wall   -c -o arg.o arg.c
gcc -Wall   -c -o readable.o readable.c
gcc -Wall   -c -o select.o select.c
gcc -Wall   -c -o sock.o sock.c
gcc -Wall   -c -o focus.o focus.c
gcc -Wall   -c -o num.o num.c
gcc -Wall   -c -o etext.o etext.c
gcc -Wall   -c -o menu.o menu.c
gcc -Wall   -c -o dialog.o dialog.c
gcc -Wall   -c -o timer.o timer.c
gcc -Wall   -c -o term.o term.c
gcc -Wall   -c -o scline.o scline.c
gcc -Wall   -c -o scpanel.o scpanel.c
gcc -Wall   -c -o tbar.o tbar.c
gcc -Wall   -c -o rszbox.o rszbox.c
gcc -Wall   -c -o scbar.o scbar.c
gcc -Wall   -c -o kbd.o kbd.c
gcc -Wall   -c -o fillbtn.o fillbtn.c
gcc -Wall   -c -o tab.o tab.c
gcc -Wall   -c -o radio.o radio.c
gcc -Wall   -c -o ckbox.o ckbox.c
gcc -Wall   -c -o button.o button.c
gcc -Wall   -c -o lblfix.o lblfix.c
gcc -Wall   -c -o fill.o fill.c
gcc -Wall   -c -o label.o label.c
gcc -Wall   -c -o panel.o panel.c
gcc -Wall   -c -o handler.o handler.c
gcc -Wall   -c -o esc.o esc.c
gcc -Wall   -c -o key.o key.c
gcc -Wall   -c -o rect.o rect.c
gcc -Wall   -c -o dbg.o dbg.c
gcc -Wall   -c -o list.o list.c
rm -f libcui.a
ar cq libcui.a cui.o arg.o readable.o select.o sock.o focus.o num.o etext.o menu.o dialog.o timer.o term.o scline.o scpanel.o tbar.o rszbox.o scbar.o kbd.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
gcc -Wall   -c -o cui_test.o cui_test.c
gcc -o cui_test cui_test.o cui.o arg.o readable.o select.o sock.o focus.o num.o etext.o menu.o dialog.o timer.o term.o scline.o scpanel.o tbar.o rszbox.o scbar.o kbd.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 -lm
gcc -Wall   -c -o cui_srv.o cui_srv.c
gcc -o cui_srv cui_srv.o -L. -lcui -lm
patching file midi_prog/Makefile
patching file midi_prog/delay.c
patching file midi_prog/delay.h
patching file midi_prog/main.c
patching file midi_prog/note.c
patching file midi_prog/note.h
patching file midi_prog/out.c
patching file midi_prog/out.h
patching file midi_prog/rd.c
patching file midi_prog/rd.h
patching file midi_prog/stat.c
patching file midi_prog/stat.h
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/vco.c
patching file midi_prog/wave.c
patching file midi_prog/wave.h
patching file midi_prog/Makefile
patching file midi_prog/main.c
patching file midi_prog/Makefile
patching file midi_prog/out.c
patching file midi_prog/Makefile
patching file midi_prog/main.c
patching file midi_prog/pth.c
patching file midi_prog/pth.h
patching file midi_prog/Makefile
patching file midi_prog/main.c
patching file midi_prog/pth.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/cui_tone.h
patching file midi_prog/main.c
patching file midi_prog/pth.c
patching file midi_prog/pth.h
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/Makefile
patching file midi_prog/main.c
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/ch.c
patching file midi_prog/ch.h
patching file midi_prog/main.c
patching file midi_prog/note.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/tone.c
patching file midi_prog/vco.c
patching file midi_prog/vco.h
patching file midi_prog/wave.c
patching file midi_prog/wave.h
patching file midi_prog/Makefile
patching file midi_prog/main.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/cui_tone.c
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/rd.c
patching file midi_prog/rd.h
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/prmfmt.txt
patching file midi_prog/txprm.c
patching file midi_prog/txprm.c
patching file midi_prog/txprm.c
patching file midi_prog/txprm.c
patching file midi_prog/Makefile
patching file midi_prog/tone.c
patching file midi_prog/Makefile
patching file midi_prog/rec.c
patching file midi_prog/rec.h
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/rec.c
patching file midi_prog/rec.h
patching file midi_prog/tone.c
patching file midi_prog/util.c
patching file midi_prog/util.h
patching file midi_prog/Makefile
patching file midi_prog/main.c
patching file midi_prog/rec.c
patching file midi_prog/rec.h
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/rec.c
patching file midi_prog/tone.c
patching file midi_prog/tone.h
patching file midi_prog/Makefile
patching file midi_prog/in.c
patching file midi_prog/in.h
patching file midi_prog/util.c
patching file midi_prog/util.h
patching file midi_prog/vcd.c
patching file midi_prog/Makefile
patching file midi_prog/vcd.c
patching file midi_prog/wrt.c
patching file midi_prog/wrt.h
patching file midi_prog/vcd.c
patching file midi_prog/vcd.c
patching file midi_prog/Makefile
patching file midi_prog/in.c
patching file midi_prog/in.h
patching file midi_prog/out.c
patching file midi_prog/out.h
patching file midi_prog/voco.c
patching file midi_prog/Makefile
patching file midi_prog/midtxt.c
patching file midi_prog/rd.c
patching file midi_prog/rd.h
patching file midi_prog/wrt.c
patching file midi_prog/Makefile
patching file midi_prog/ch.c
patching file midi_prog/ch.h
patching file midi_prog/main.c
patching file midi_prog/note.c
patching file midi_prog/note.h
patching file midi_prog/midtxt.c
patching file midi_prog/ch.c
patching file midi_prog/midtxt.c
patching file midi_prog/out.c
patching file midi_prog/util.c
patching file midi_prog/util.h
patching file midi_prog/voco.c
patching file midi_prog/midtxt.c
patching file midi_prog/util.c
patching file midi_prog/util.h
gcc -Wall -I..   -c -o main.o main.c
gcc -Wall -I..   -c -o vcf.o vcf.c
gcc -Wall -I..   -c -o ch.o ch.c
gcc -Wall -I..   -c -o delay.o delay.c
gcc -Wall -I..   -c -o stat.o stat.c
gcc -Wall -I..   -c -o note.o note.c
gcc -Wall -I..   -c -o env.o env.c
gcc -Wall -I..   -c -o tone.o tone.c
gcc -Wall -I..   -c -o filter.o filter.c
gcc -Wall -I..   -c -o lfo.o lfo.c
gcc -Wall -I..   -c -o modu.o modu.c
gcc -Wall -I..   -c -o vco.o vco.c
gcc -Wall -I..   -c -o wave.o wave.c
gcc -Wall -I..   -c -o out.o out.c
gcc -Wall -I..   -c -o rd.o rd.c
gcc -Wall -I..   -c -o util.o util.c
gcc -Wall -I..   -c -o rec.o rec.c
gcc -Wall -I..   -c -o cui_tone.o cui_tone.c
gcc -o prog59 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 rec.o cui_tone.o -lm -lpthread -L../cui -lcui
gcc -Wall -I..   -c -o vcd.o vcd.c
gcc -Wall -I..   -c -o in.o in.c
gcc -Wall -I..   -c -o wrt.o wrt.c
gcc -o vcd vcd.o in.o filter.o out.o wrt.o util.o -lm
gcc -Wall -I..   -c -o voco.o voco.c
gcc -o voco voco.o vco.o wave.o in.o filter.o out.o util.o -lm
gcc -Wall -I..   -c -o midtxt.o midtxt.c
gcc -o midtxt midtxt.o rd.o wrt.o util.o
patching file ptmsk-0.tone
patching file ptmsk-0.sh
patching file ptmsk-1.sh
patching file ptmsk-2.sh
patching file ptmsk-3.sh
patching file ptmsk-4.sh
patching file ptmsk-5.sh
patching file ptmsk-6.sh
patching file ptmsk-7.sh
patching file ptmsk-8.sh
patching file ptmsk-9.sh
patching file ptmsk-10.sh
patching file ptmsk-10.tone
patching file ptmsk-11.sh
patching file ptmsk-11.tone
patching file ptmsk-12.sh
patching file ptmsk-12.tone
patching file ptmsk-13.sh
patching file midi_prog/Makefile
patching file midi_prog/midtxt.c
patching file midi_prog/note.c
patching file midi_prog/tone.c
rm -f prog60 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 rec.o cui_tone.o *~
rm -f vcd vcd.o in.o filter.o out.o wrt.o util.o voco voco.o vco.o wave.o in.o filter.o out.o util.o
rm -f midtxt midtxt.o rd.o wrt.o util.o
patching file out.c
gcc -Wall -I..   -c -o main.o main.c
gcc -Wall -I..   -c -o vcf.o vcf.c
gcc -Wall -I..   -c -o ch.o ch.c
gcc -Wall -I..   -c -o delay.o delay.c
gcc -Wall -I..   -c -o stat.o stat.c
gcc -Wall -I..   -c -o note.o note.c
gcc -Wall -I..   -c -o env.o env.c
gcc -Wall -I..   -c -o tone.o tone.c
gcc -Wall -I..   -c -o filter.o filter.c
gcc -Wall -I..   -c -o lfo.o lfo.c
gcc -Wall -I..   -c -o modu.o modu.c
gcc -Wall -I..   -c -o vco.o vco.c
gcc -Wall -I..   -c -o wave.o wave.c
gcc -Wall -I..   -c -o out.o out.c
gcc -Wall -I..   -c -o rd.o rd.c
gcc -Wall -I..   -c -o util.o util.c
gcc -Wall -I..   -c -o rec.o rec.c
gcc -Wall -I..   -c -o cui_tone.o cui_tone.c
gcc -o prog60 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 rec.o cui_tone.o -lm -lpthread -L../cui -lcui
gcc -Wall -I..   -c -o vcd.o vcd.c
gcc -Wall -I..   -c -o in.o in.c
gcc -Wall -I..   -c -o wrt.o wrt.c
gcc -o vcd vcd.o in.o filter.o out.o wrt.o util.o -lm
gcc -Wall -I..   -c -o voco.o voco.c
gcc -o voco voco.o vco.o wave.o in.o filter.o out.o util.o -lm
gcc -Wall -I..   -c -o midtxt.o midtxt.c
gcc -o midtxt midtxt.o rd.o wrt.o util.o
play WARN sox: Option `-s' is deprecated, use `-e signed-integer' instead.

-: (raw)

  Encoding: Signed PCM    
  Channels: 2 @ 16-bit   
Samplerate: 44100Hz      
Replaygain: off         
  Duration: unknown      

In:0.00% 00:00:05.14 [00:00:00.00] Out:227k  [      |      ]        Clip:0    
Done.
$ 

Ubuntu16な環境で midi_dl_build2.sh を試すと、sh ptmsk-14.sh の実行でひっかかりました。 「sh はどうせ bash なので...」などと、決めつけていた箇所がダメでした。 1箇所修正入れときます。

midi_dl_build2.sh-190528.patch
$ cat midi_dl_build2.sh-190528.patch | patch -p0
$ mkdir -p /tmp/work
$ cp midi_dl_build2.sh /tmp/work/
$ cd /tmp/work
$ sh midi_dl_build2.sh 
  :


音色お試しスクリプト

久々にビルドしたついでに、 CUIで音色を変えてみる を試そうとすると、もっさり動作にうんざりでした。

思い出しがてら、2つの音色のパラメータを入力して、 その間のパラメータの音色を試すスクリプトを組んでみました。

test_tone.tgz

先の ダウンロードとビルドのまとめ 2018/Feb で展開した先の、midi_prog/ ディレクトリ内に展開して試します。

$ mkdir -p /tmp/work
$ cp midi_dl_build2.sh /tmp/work/
$ cd /tmp/work
$ sh midi_dl_build2.sh 
  :

を実行した上で

$ cp test_tone.tgz /tmp/work/midi_prog
$ cd /tmp/work/midi_prog

$ tar tvzf test_tone.tgz
-rw-r--r--  0 kondoh staff    2152  2 22 12:41 test_tone.c
-rwxr-xr-x  0 kondoh staff     645  2 22 12:59 test_tone.sh
-rw-r--r--  0 kondoh staff    1810  2 22 12:56 test_tone.tone
-rw-r--r--  0 kondoh staff     796  2 22 13:14 test_tone_Makefile.patch

$ tar xzf test_tone.tgz

$ cat test_tone_Makefile.patch | patch -p0
patching file Makefile

$ make
gcc -Wall -I..   -c -o test_tone.o test_tone.c
gcc -o test_tone test_tone.o rec.o tone.o modu.o lfo.o delay.o ch.o vcf.o stat.o rd.o filter.o vco.o wave.o util.o

$ sh test_tone.sh

play WARN sox: Option `-s' is deprecated, use `-e signed-integer' instead.

-: (raw)

  Encoding: Signed PCM    
  Channels: 2 @ 16-bit   
Samplerate: 44100Hz      
Replaygain: off         
  Duration: unknown      

In:0.00% 00:00:10.00 [00:00:00.00] Out:441k  [      |      ]        Clip:0    
Done.

スクリプト概略

test_tone.tone の中に "foo", "bar" の2つの音色があります。 スクリプト test_tone.sh では冒頭の

IN=test_tone.tone
OUT=test_tone.out.tone

./test_tone < $IN > $OUT

の箇所で、1つ目の音色から2つ目の音色に、 32段階で変化させた音色設定を "test_tone.out.tone" ファイルに出力してます。

出力したファイルの後半は、32の音色をMIDIのプログラム番号0から31に登録する設定になってます。

test_tone.sh に戻って、CH=0 以降の箇所では、 プログラム番号を0から31まで切り替えながら、 ノート番号69のON/OFFを繰り返すMIDIファイル test_tone.mid を生成してます。

最後の箇所

( echo load ; cat $OUT test_tone.mid ) | ./prog60 -play

で、test_tone.out_tone の音色設定をロードして、 test_tone.mid のMIDIファイルの再生を試してます。

段数を可変に

少々修正。 32段階の固定が不便なので可変にしてみます。

と言っても実行時の引数指定じゃなくて test_tone.sh にベタ書きですが...

test_tone2.patch

$ cp test_tone2.patch /tmp/work/midi_prog/
$ cd /tmp/work/midi_prog
$ cat test_tone2.patch | patch -p1 
patching file test_tone.c
patching file test_tone.sh
patching file test_tone.tone

$ make
gcc -Wall -I..   -c -o test_tone.o test_tone.c
gcc -o test_tone test_tone.o rec.o tone.o modu.o lfo.o delay.o ch.o vcf.o stat.o rd.o filter.o vco.o wave.o util.o

$ sh mid_dl_build2.sh
  :

段数の変更は test_tone.sh の

N=16

の箇所を書き換えて

$ sh mid_dl_build2.sh
  :


工事中...