diff --git a/doc/CHANGES b/doc/CHANGES index 5dbffdc..360df4e 100644 --- a/doc/CHANGES +++ b/doc/CHANGES @@ -15675,3 +15675,16 @@ February 24 2026 midistats: the summary includes ntimesig, triplets, unquantized when appropriate. +March 30 2026 [RK] + +abc2midi: added -PMAR option to emit MIDI marker meta-events (0x06) for +P: part labels. When a header P: field specifies a play order (e.g. +P:(AB)3), a marker is emitted each time a section begins during the +expanded playback, with an instance number (e.g. "Part A-1", "Part B-2"). +When no header P: field is present, inline P: labels in the body are +emitted as simple section markers (e.g. "Part A"). The existing code at +genmidi.c:3187 that was meant to write markers was unreachable dead code; +it has been restructured. Changes in genmidi.c (PART case in writetrack, +partmarkers global) and store.c (event_part, event_init for -PMAR flag). +Man page updated in doc/abc2midi.1. + diff --git a/doc/abc2midi.1 b/doc/abc2midi.1 index f548895..9084fa1 100644 --- a/doc/abc2midi.1 +++ b/doc/abc2midi.1 @@ -2,7 +2,7 @@ .SH NAME \fBabc2midi\fP \- converts abc file to MIDI file(s) .SH SYNOPSIS -abc2midi \fIinfile\fP [\fIrefnum\fP] [\-c] [\-v] [\-ver] [\-t] [\-n limit] [\-CS] [\-quiet] [\-silent] [\-Q tempo] [\-NFNP] [\-NFER] [\-NGRA] [\-NGUI] [\-STFW] [\-OCC] [\-NCOM] [\-HARP] [\-BF] [\-TT] [\-o outfile] \-CSM [filename] +abc2midi \fIinfile\fP [\fIrefnum\fP] [\-c] [\-v] [\-ver] [\-t] [\-n limit] [\-CS] [\-quiet] [\-silent] [\-Q tempo] [\-NFNP] [\-NFER] [\-NGRA] [\-NGUI] [\-STFW] [\-OCC] [\-NCOM] [\-PMAR] [\-HARP] [\-BF] [\-TT] [\-o outfile] \-CSM [filename] .SH DESCRIPTION The default action is to write a MIDI file for each abc tune with the filename N.mid, where is the filestem @@ -61,6 +61,14 @@ Place lyric text in separate MIDI tracks. .B -NCOM Suppress some comments in the output MIDI file. .TP +.B -PMAR +Emit MIDI marker meta-events for P: part labels. When a header P: field +specifies a play order (e.g. P:(AB)3), a marker is emitted each time a +section begins during the expanded playback, with an instance number +appended (e.g. "Part A-1", "Part B-1", "Part A-2", "Part B-2"). +When no header P: field is present, inline P: labels in the body are +emitted as simple section markers (e.g. "Part A", "Part B"). +.TP .B -OCC Accept old chord convention (eg +D2G2+ instead of [DG]2). .TP diff --git a/genmidi.c b/genmidi.c index ce06c54..acf1ac6 100644 --- a/genmidi.c +++ b/genmidi.c @@ -105,6 +105,7 @@ int gchordbars; /* Part handling */ extern struct vstring part; int parts, partno, partlabel; +int partmarkers; /* -PMAR flag: emit MIDI marker meta-events for P: parts [RK] 2026-03-30 */ int part_start[26], part_count[26]; long introlen, lastlen, partlen[26]; int partrepno; @@ -3123,17 +3124,33 @@ long writetrack(int xtrack) checksyllables(); }; break; - case PART: + case PART: /* [RK] 2026-03-30 */ in_varend = 0; - /*j = partbreak(xtrack, trackvoice, j); [SS] 2023.01.20 */ - j = findvoice(j, trackvoice, xtrack); - if (parts == -1) { - char msg[1]; - - msg[0] = (char) pitch[j]; - mf_write_meta_event(0L, marker, msg, 1); - }; + /* No header P: spec, body labels only: emit "Part X" */ + if (partmarkers && xtrack == 0 && + pitch[j] >= 'A' && pitch[j] <= 'Z') { + char msg[8]; + snprintf(msg, sizeof(msg), "Part %c", (char) pitch[j]); + mf_write_meta_event(delta_time_track0, marker, msg, strlen(msg)); + tracklen = tracklen + delta_time_track0; + delta_time_track0 = 0L; + } + } else { + /* Parts active, navigate then emit "Part X-N" marker + where N is the instance number (1-based) */ + /*j = partbreak(xtrack, trackvoice, j); [SS] 2023.01.20 */ + j = findvoice(j, trackvoice, xtrack); + if (partmarkers && xtrack == 0 && + partlabel >= 0 && partlabel < 26) { + char msg[20]; + snprintf(msg, sizeof(msg), "Part %c-%d", + (char)(partlabel + 'A'), part_count[partlabel]); + mf_write_meta_event(delta_time_track0, marker, msg, strlen(msg)); + tracklen = tracklen + delta_time_track0; + delta_time_track0 = 0L; + } + } break; case VOICE: /* search on for next occurrence of voice */ diff --git a/store.c b/store.c index 470bb92..2e223e5 100755 --- a/store.c +++ b/store.c @@ -422,6 +422,7 @@ int apply_fermata_to_chord = 0; /* [SS] 2012-03-26 */ /* Part handling */ struct vstring part; extern int parts, partno, partlabel; +extern int partmarkers; /* [RK] 2026-03-30 */ extern int part_start[26], part_count[26]; int voicesused; @@ -945,6 +946,13 @@ void event_init(int argc, char *argv[], char **filename) nocom = 0; } + /* [RK] 2026-03-30 */ + if (getarg("-PMAR", argc, argv) != -1) { + partmarkers = 1; + } else { + partmarkers = 0; + } + if (getarg("-STFW",argc,argv) != -1) { separate_tracks_for_words = 1; } else { @@ -1025,7 +1033,7 @@ void event_init(int argc, char *argv[], char **filename) printf("abc2midi version %s\n",VERSION); printf("Usage : abc2midi [reference number] [-c] [-v] "); printf("[-o filename]\n"); - printf(" [-t] [-n ] [-CS] [-NFNP] [-NCOM] [-NFER] [-NGRA] [-NGUI] [-HARP]\n"); + printf(" [-t] [-n ] [-CS] [-NFNP] [-NCOM] [-NFER] [-NGRA] [-NGUI] [-HARP] [-PMAR]\n"); printf(" [reference number] selects a tune\n"); printf(" -c selects checking only\n"); printf(" -v selects verbose option\n"); @@ -1039,6 +1047,7 @@ void event_init(int argc, char *argv[], char **filename) printf(" -Q default tempo (quarter notes/minute)\n"); printf(" -NFNP don't process !p! or !f!-like fields\n"); printf(" -NCOM suppress comments in output MIDI file\n"); + printf(" -PMAR emit MIDI marker meta-events for P: part labels\n"); /* [RK] 2026-03-30 */ printf(" -NFER ignore all fermata markings\n"); printf(" -NGRA ignore grace notes\n"); printf(" -NGUI ignore guitar chord indications\n"); @@ -2893,7 +2902,13 @@ void event_part(char *s) if (dotune) { p = s; skipspace(&p); - if (pastheader && parts == -1) return; /* [SS] 2014-04-10 */ + if (pastheader && parts == -1) { /* [RK] 2026-03-30 */ + /* No header P: spec. If -PMAR, store body P: as section label */ + if (partmarkers && ((int)*p >= 'A') && ((int)*p <= 'Z')) { + addfeature(PART, (int)*p, 0, 0); + } + return; + } if (pastheader) { if (((int)*p < 'A') || ((int)*p > 'Z')) { if (!silent) event_error("Part must be one of A-Z");