/* *** midtoseq.c: SMF ファイルの疑似シーケンサ出力 *** * (1999.8.24 / rev. 2017.10.31: YH) * * * 使用法: midtoseq [options] [SMF-file] * 出力は stdout へ。 *** SMF ファイル中のノート情報を、時間順に整理して提示する。 * * * options * -b: 拍ごとの出力を表示しない(デフォールトではノートオンがなくても * 拍ごとの表示を行う)。 * -z: 音長 0 の音符は出力しない。 * -v[n]: メッセージレベルを n に設定する。n=0, 1, 2 の順に詳細になる。 * 省略時には n=1 となる。 * *** 問題点 * * プログラムとしてはまだ整理可能。また分割コンパイルへの移行も考えるべき。 * * 音名出力は不十分(#(シャープ)、b(フラット)の使い分け)。 * * グラフィカル・シーケンサ的な出力・表示プログラムは別途に作成する。 */ #include #include #include #include // extern char *malloc(); /* option flags */ int beatflag = 1; /* ノートオンのない拍も出力 */ int zeroflag = 0; /* 1 なら音長 0 の音符は出力しない */ int verbos = 0; /* メッセージ出力レベル: 0, 1, 2 (, ...) */ #define MAXLEN 1000000 /* SMF ファイル読み込みバッファの大きさ */ #define MAXEVENT 500000 /* 内部イベントバッファの大きさ */ #define MAXTIMELINE 100000 /* 時系列バッファの大きさ */ #define MAXTRACK 127 /* トラックの最大数 */ #define MAXTIMESIG 1000 /* 拍子記号の最大数 */ #define MAXMEASURE 10000 /* 小節の最大数 */ int format; /* フォーマット: 0/1/2 */ int ntrack; /* トラック数 */ int tqtm; /* 時間単位 */ int ntrk; /* 現在のトラック番号 (0, 1, 2, ...) */ int ttime; /* 現在のトラック中の通算時間 */ int maxtime; /* ttime の最大値 */ int status; /* イベントのステータス・バイト */ int mp0; /* イベント(ないしチャンク)の先頭バイト */ int trackbytes; /* トラック・データセクションのバイト数 */ int nrevent; /* 総イベント数 */ int nevent; /* 総有効イベント数(ペアをまとめたもの) */ int nnote; /* 総ノート数 */ int ntrevent[MAXTRACK]; /* トラックのイベント数 */ int ntevent[MAXTRACK]; /* トラックの有効イベント数 */ int ntnote[MAXTRACK]; /* トラックのノート数 */ int nmeasure = 0; /* 現在までの小節数 */ int beat_unit, beat_onset; /* 拍の長さ、次の拍の開始点 */ /* *** 構造体データ *** */ /* 内部ステータス表現:実際には一部のみ使用 */ #define MIDI_NOTE_OFF 0x8000 #define MIDI_NOTE_ON 0x9000 #define MIDI_POLY_KEY_PRESSURE 0xa000 #define MIDI_CONTROL_CHANGE 0xb000 #define MIDI_PROGRAM_CHANGE 0xc000 #define MIDI_CHANNEL_PRESSURE 0xd000 #define MIDI_PITCH_BEND 0xe000 #define MIDI_DAMPER_PEDAL 0xb040 #define MIDI_SYSTEM_EXCLUSIVE 0xf000 #define MIDI_QUARTER_FRAME 0xf100 #define MIDI_SONG_POSITION 0xf200 #define MIDI_SONG_SELECT 0xf300 #define MIDI_CHAIN_REQUEST 0xf600 #define MIDI_TIMING_CLOCK 0xf800 #define MIDI_START 0xfa00 #define MIDI_CONTINUE 0xfb00 #define MIDI_STOP 0xfc00 #define MIDI_ACTIVE_SENSING 0xfe00 #define META_PREFIX 0xff00 #define META_SEQUENCE_NUMBER 0xff00 #define META_TEXT 0xff01 #define META_COPYRIGHT 0xff02 #define META_TRACK_NAME 0xff03 #define META_INSTRUMENT 0xff04 #define META_LYRIC 0xff05 #define META_MARKER 0xff06 #define META_CUE_POINT 0xff07 #define META_END_OF_TRACK 0xff2f #define META_TEMPO 0xff51 #define META_SMPTE_OFFSET 0xff54 #define META_TIME_SIGNATURE 0xff58 #define META_KEY_SIGNATURE 0xff59 #define META_MAKER_SPECIFIC 0xff7f #define Ev_channel(ev) (((ev)->status >> 8) & 0x0f) #define isnoteon(ev) (((ev)->status & 0xf000) == MIDI_NOTE_ON) #define isnoteoff(ev) (((ev)->status & 0xf000) == MIDI_NOTE_OFF) #define isnote(ev) (isnoteon(ev) || isnoteoff(ev)) #define isdamper(ev) (((ev)->status & 0xf0ff) == MIDI_DAMPER_PEDAL) struct EVENT { /* イベント */ int onset; /* 開始時刻 */ int track; /* トラック番号 */ int status; /* ステータスまたは拡張ステータス */ int pitch, velocity; /* ノート番号、音量 */ int length; /* ノート等の時間長 or data の長さ */ unsigned char *data; /* データバイト列へのポインタ */ } events[MAXEVENT], *pevents[MAXEVENT]; int evp = 0, ep = 0, xp = 0; #define data1 pitch /* pitch フィールドの別名 */ #define data2 velocity /* velocity フィールドの別名 */ struct TIMESIG { /* 拍子 */ int origin; /* 開始時刻 */ int n, d; /* 拍子記号の分子・分母 */ int mlen, blen; /* 小節、拍の長さ */ int upbeat; /* アウフタクト(前打音)の長さ(=0: なし) */ int moffset; /* 小節番号のオフセット */ } timesigs[MAXTIMESIG]; int tsp = 0; struct TIMELINE { /* 時系列 */ int seq; /* 通し番号 */ int onset; /* 時刻 */ int measure, beat, offset; /* 小節番号、拍番号、拍内変位 */ struct TIMESIG *timesig; /* 現在の拍子 */ struct EVENT *key; /* 現在の調 */ struct EVENT **notes; /* ノートイベントのポインタ配列 */ struct EVENT **etc; /* その他のイベントのポインタ配列 */ int nn, ne; /* notes, etc の要素数 */ } timelines[MAXTIMELINE]; int tlp = 0, ntimeline; /* *** Phase 0: MIDI ファイル読み込み *** */ FILE *fin; char filename[100]; /* 入力 MIDI ファイル名 */ unsigned char mbuf[MAXLEN]; /* MIDI ファイル読み込みバッファ */ int mlen; /* MIDI ファイルの長さ(バイト数) */ int mp; /* 現在のバイト位置 */ void readfile() /* MIDI ファイルを内部バッファに読み込む */ { int c; mlen = 0; while ((c = getc(fin)) != EOF) { if (mlen >= MAXLEN) { fprintf(stderr, "*** MIDI file too large ***\n"); exit(1); } mbuf[mlen++] = c; } mp = 0; } /* *** Phase 1: イベント切り出し *** */ static struct EVENT *ev; #define getb() mbuf[mp++] /* 1バイトの読み込み */ #define ungetb() mp-- int getn(b) /* 固定長数値の読み込み b: バイト数 */ int b; { int n, i; for (i=0, n=0; i= MAXEVENT) { fprintf(stderr, "event buffer overflow\n"); exit(1); } ev = &(events[evp++]); ev->onset = ttime; ev->track = ntrk; ev->status = 0; ev->pitch = 0; ev->velocity = 0; ev->length = 0; ev->data = NULL; return(ev); } void free_event() /* 仮割り当てしたイベントを返還 */ { evp--; ntevent[ntrk]--; } /* *** HeaderChunk(): ヘッダ・チャンクの読み込み・表示 *** */ void HeaderChunk() { int len; mp0 = mp; if (strncmp((char *)(mbuf+mp), "MThd", 4)) { /* first 4 bytes are "MThd"? */ printf("*** file is not a proper MIDI file ***\n"); exit(1); } mp += 4; len = getn(4); format = getn(2); ntrack = getn(2); tqtm = getn(2); } /* *** トラック読み込み *** */ void event_error(rc) /* エラー・警告メッセージ: rc>0: 異常終了 */ int rc; { int i; printf("byte %04d:", mp0); for (i = 0; i<10; i++) printf(" %02x", mbuf[mp0+i]); printf("\n"); if (rc > 0) { printf("\t*** unknown event, aborting ***\n"); exit(1); } } static void remove_event(i) /* pevents スタックから第 i 要素を削除 */ int i; { ep--; for (; istatus & 0xfff)): ev->status; /* -1: "note-off" イベントであることを示す(vel. チェック用) */ int vel = (isnoteoff(ev))? -1: ev->velocity; for (i=0; istatus) { /* FOUND */ if (vel == pevents[i]->velocity) { /* 同じ velocity:現在のイベントを継続する。 */ if (verbos > 1) { event_error(0); printf("\t*** same velocity: continuing ***\n"); } free_event(); return; } /* 現在のイベントをオフにする */ pevents[i]->length = ev->onset - pevents[i]->onset; remove_event(i); if (vel <= 0) { /* note off */ if ((vel < 0) && (ev->velocity > 0)) { /* note off velocity がある場合:ポインタを設定 */ pevents[i]->data = mbuf + (mp-1); if (verbos > 1) { event_error(0); printf("\t*** note has OFF velocity ***\n"); } } free_event(); return; } /* 同一音が違う velocity で継続:下の NOT FOUND に進む */ if (verbos > 1) { event_error(0); printf("\t*** same event with different velocity ***\n"); } break; } /* NOT FOUND */ if (vel > 0) { /* 新しいオン・イベント:スタックに登録 */ if (isnote(ev)) ntnote[ntrk]++; pevents[ep++] = ev; return; } /* 対応するオンのないオフ・イベント:無視する。 */ if (verbos > 1) { event_error(0); printf("\t*** extraneous OFF message: ignoring ***\n"); } free_event(); } void MIDI_event() { ev->status = status << 8; /* 追加情報のため、下8ビットをあける */ switch(status & 0xf0) { case 0x80: /* note off */ case 0x90: /* note on */ case 0xa0: /* polyphonic key pressure */ ev->pitch = getn(1); ev->velocity = getn(1); ev->status |= ev->pitch; if ((status & 0xf0) != 0xa0) match_onoff(ev); break; case 0xb0: /* control change */ ev->data1 = getn(1); /* dummy */ ev->data2 = getn(1); ev->status |= ev->data1; /* LSB: data byte 1 */ if (ev->data1 == 0x40) match_onoff(ev); /* hold 1 on/off */ break; case 0xc0: /* program change */ case 0xd0: /* channel pressure */ ev->data1 = ev->data2 = getn(1); break; case 0xe0: /* pitch bend */ ev->data1 = getn(1); ev->data1 |= getn(1) << 7; ev->data1 -= 0x2000; ev->data2 = ev->data1; break; case 0xf0: switch(status & 0x0f) { case 0x01: /* quarter frame */ case 0x03: /* song select */ ev->data1 = ev->data2 = getn(1); break; case 0x02: /* song position pointer */ ev->data1 = getn(1); ev->data1 |= getn(1) << 7; ev->data2 = ev->data1; break; case 0x06: /* tune request */ break; case 0x08: /* timing clock */ case 0x0a: /* start */ case 0x0b: /* continue */ case 0x0c: /* stop */ case 0x0e: /* active sensing */ break; default: event_error(1); } break; default: event_error(1); /* shouldn't happen */ } } void SysEx_event() { int len = getv(); ev->status = MIDI_SYSTEM_EXCLUSIVE; ev->data = mbuf + mp; ev->length = len; if (status == 0xf0) ev->length--; /* 末尾の 0xf7 を削除 */ mp += len; } void Meta_event() { int len, event_type, base; event_type = getb(); len = getv(); base = mp; ev->status = META_PREFIX | event_type; /* 0xff + 第2バイト */ ev->data = mbuf + base; ev->length = len; switch(event_type) { /* データ抽出しておくケース */ case 0x00: /* sequence number */ ev->data1 = getn(2); break; case 0x51: /* set tempo */ ev->data1 = getn(3); break; case 0x58: /* time signature */ ev->data1 = getn(1); /* 拍子の分子 */ ev->data2 = 1 << getn(1); /* 拍子の分母 */ break; case 0x59: /* key signature */ ev->data1 = getn(1); /* +:シャープ、-: フラット */ if (ev->data1 & 0x80) ev->data1 -= 0x100; ev->data2 = getn(1); /* 0: 長調、1: 短調 */ break; case 0x01: /* text */ case 0x02: /* copyright notice */ case 0x03: /* sequence/track name */ case 0x04: /* instrument */ case 0x05: /* lyric */ case 0x06: /* marker */ case 0x07: /* cue point */ /* other texts */ case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x20: /* MIDI channel prefix */ case 0x21: /* (patch) */ case 0x2f: /* end of track */ case 0x54: /* SMPTE offset */ case 0x7f: /* maker specific */ break; default: event_error(1); } mp = base + len; } void get_event() /* イベント1コの処理 */ { int b, delta, savstatus; mp0 = mp; /* イベントの開始位置 */ delta = getv(); ttime += delta; ev = new_event(); ntrevent[ntrk]++; ntevent[ntrk]++; b = getb(); if (b & 0x80) { /* ステータスバイト */ savstatus = status; switch(status = b) { default: MIDI_event(); if (status >= 0xf8) status = savstatus; /* リアルタイムメッセージはランニング ステータスに影響しない */ break; case 0xf0: case 0xf7: SysEx_event(); break; case 0xff: Meta_event(); status = savstatus; /* たぶん?ランニングステータスに 影響しない。 */ break; } } else { /* ランニングステータス */ ungetb(); MIDI_event(); } } void TrackChunk() /* トラック・チャンク処理 */ { int endtrack; mp0 = mp; ttime = 0; ntrevent[ntrk] = 0; ntevent[ntrk] = 0; ntnote[ntrk] = 0; ep = 0; /* ここでは pevents[] をオン・オフ処理スタックに使用 */ /* ヘッダ部分 */ if (strncmp((char *)(mbuf+mp), "MTrk", 4)) { /* first 4 bytes are "MTrk"? */ /* error: shouldn't happen */ } mp += 4; trackbytes = getn(4); endtrack = mp + trackbytes; while (mp < endtrack) get_event(); if (ep > 0) { /* 未解決のオン・イベントがある */ printf("*** track %d: exist %d unresolved event(s):", ntrk, ep); printf("duration set to 0 ***\n"); } nrevent += ntrevent[ntrk]; nevent += ntevent[ntrk]; nnote += ntnote[ntrk]; if (ttime > maxtime) maxtime = ttime; if (verbos) { printf("==== track %d: %3d raw events %3d effective events %3d notes\n", ntrk, ntrevent[ntrk], ntevent[ntrk], ntnote[ntrk]); } } /* *** phase 2:時系列生成 *** */ int tp[MAXTRACK]; /* トラック先頭位置 */ int tt[MAXTRACK]; /* トラック終了位置 */ struct TIMESIG * new_timesig(o, n, d) /* 拍子構造体の割り当て */ int o, n, d; { struct TIMESIG *ts; if (tsp >= MAXTIMESIG) { fprintf(stderr, "timesig buffer overflow\n"); exit(1); } ts = &(timesigs[tsp++]); ts->origin = o; ts->n = n; ts->d = d; beat_unit = ts->blen = tqtm * 4 / d; beat_onset = o + beat_unit; ts->mlen = ts->blen * n; ts->moffset = nmeasure; ts->upbeat = 0; /* currently not set */ return(ts); } void metrictime(tl, ts) /* 経過時間の小節・拍番号への変換 */ struct TIMELINE *tl; struct TIMESIG *ts; { int offset; if (ts == NULL) { /* 拍子指定なし */ tl->measure = 0; tl->beat = (tl->onset / tqtm) + 1; tl->offset = tl->onset % tqtm; return; } offset = tl->onset - ts->origin; if (ts->upbeat > 0) offset += ts->mlen - ts->upbeat; nmeasure = ts->moffset + offset / ts->mlen; tl->measure = nmeasure + 1; tl->beat = (offset % ts->mlen) / ts->blen + 1; tl->offset = offset % ts->blen; } struct TIMELINE * new_timeline(onset) /* 時系列構造体の割り当て */ int onset; { struct TIMELINE *ptl = NULL, *tl; if (tlp >= MAXTIMELINE) { fprintf(stderr, "timeline buffer overflow\n"); exit(1); } if (tlp > 0) { ptl = &(timelines[tlp-1]); } tl = &(timelines[tlp]); tl->seq = tlp++; tl->onset = onset; if (ptl) { tl->timesig = ptl->timesig; tl->key = ptl->key; } else { tl->timesig = NULL; tl->key = NULL; } metrictime(tl, tl->timesig); tl->notes = pevents + ep; tl->etc = pevents + xp; tl->nn = 0; tl->ne = 0; return(tl); } int timeline1() /* 1コの時系列項目の生成 */ { int i, j, onset = maxtime + 1; struct EVENT *ev; struct TIMELINE *tl; for (i=0; i= tt[i]) continue; if ((j = events[tp[i]].onset) < onset) onset = j; } if (beatflag) { /* 拍位置を必ず出力する場合 */ while (beat_onset < onset) { new_timeline(beat_onset); beat_onset += beat_unit; } if (beat_onset == onset) beat_onset += beat_unit; } if (onset > maxtime) return(0); /* 全イベントの処理終了 */ tl = new_timeline(onset); for (i=0; ionset > onset) break; if (isnote(ev)) { /* ノートイベント */ /* これで tl->notes への登録になっている */ pevents[ep++] = ev; tl->nn++; /* トラック内では高い順に並べる */ for (j=ep-1; j > ep0; j--) if (pevents[j]->pitch > pevents[j-1]->pitch) { ev = pevents[j]; pevents[j] = pevents[j-1]; pevents[j-1] = ev; } else break; continue; } /* その他のイベント */ pevents[xp++] = ev; tl->ne++; if (ev->status == META_TIME_SIGNATURE) { tl->timesig = new_timesig(onset,ev->data1,ev->data2); metrictime(tl, tl->timesig); } else if (ev->status == META_KEY_SIGNATURE) { tl->key = ev; } } } return(1); } void generate_timeline() /* 時系列生成 */ { int i; /* 各トラックの走査位置設定 */ tp[0] = 0; tt[0] = ntevent[0]; for (i=1; i 0) { trackcol[i] = notetrack; coltrack[notetrack++] = i; } } highnote = 0; lownote = 127; lastonset = 0; for (i=0; ipitch > highnote) highnote = pevents[i]->pitch; if (pevents[i]->pitch < lownote) lownote = pevents[i]->pitch; if (pevents[i]->onset + pevents[i]->length > lastonset) lastonset = pevents[i]->onset + pevents[i]->length; } for (ntimeline = tlp-1;; ntimeline--) { if (timelines[ntimeline].onset < lastonset) { ntimeline++; break; } } } void print_head(tl, flag) /* 行頭部分の出力 */ struct TIMELINE *tl; int flag; { int i; if (tl->measure == 0) { printf("%05d:%03d ", tl->beat, tl->offset); return; } switch (flag) { case 0: /* full output */ printf("%03d:%01d:%03d ", tl->measure, tl->beat, tl->offset); break; case 1: /* measure indicator */ printf("%03d:--------", tl->measure); for (i=0; ibeat, tl->offset); break; case 3: /* note additional lines */ printf(" "); break; } } void print_note(nt, track) /* ノートの出力 */ struct EVENT *nt; int track; { int i; if (nt == NULL) { for (i=0; itrack == track) { printf(" | "); return; } } printf(" "); return; } printf("%d%s[%03d] ", nt->pitch/12-1, notenames[nt->pitch % 12][sf], nt->length); if (nt->length < 1000) printf(" "); } void print_notes(tl) /* ノート列の出力 */ struct TIMELINE *tl; { struct EVENT **notes = tl->notes; int nn = tl->nn; static struct EVENT *pnotes[MAXTRACK][10]; static int nnt[MAXTRACK]; int i, j, maxn = 1, t; for (i=0; itrack]; if (!zeroflag || (notes[i]->length > 0)) { pnotes[t][nnt[t]++] = notes[i]; if (nnt[t] > maxn) maxn = nnt[t]; } } for (i=0; ionset + pstack[i]->length > onset) { i++; continue; } pstack[i] = pstack[--ep]; } } void print_result() { int i, j; int measure = 0; sf = 0; pstack = pevents + nevent; /* 継続ノートのスタック */ ep = 0; for (i=0; i measure)) { /* 小節境界の出力(必須) */ print_head(&timelines[i], 1); measure++; } clearstack(timelines[i].onset); if (timelines[i].nn == 0) { if (!beatflag || timelines[i].offset > 0) continue; } if (timelines[i].key != NULL) { sf = (timelines[i].key->data1 >= 0)? 0: 1; } for (j=0; j %d(%d%s)]\n", lownote, lownote/12-1, notenames[lownote % 12][0], highnote, highnote/12-1, notenames[highnote % 12][0]); if (timelines[0].key != NULL) printf("*** key signature : %s\n", keyname[timelines[0].key->data1 + 7][timelines[0].key->data2]); if (timelines[0].timesig != NULL) printf("*** time signature: %d/%d\n", timelines[0].timesig->n, timelines[0].timesig->d); printf("\n"); print_result(); }