From ce70bc4d4e3ea725df695a29f5622b201b4fbda5 Mon Sep 17 00:00:00 2001 From: Ronan Keryell Date: Wed, 22 Apr 2026 04:58:22 -0700 Subject: [PATCH] Add missing C: (composer), R: (rhythm) and X: fields and return error (#21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add configuration for CMake build system alongside autoconf - Add a modern CMake build system (`CMakeLists.txt`, `CMakePresets.json`) that coexists with the legacy autoconf/Makefile build - Shared source files (`midifile.c`, `parseabc.c`, `music_utils.c`, `parser2.c`) are compiled once via OBJECT libraries and linked into the 8 binaries - Three presets: `default` (Release), `debug`, `sanitize` (ASan + UBSan) - Generates `compile_commands.json` for clangd/LSP editor support - Install rules match the legacy Makefile (binaries, doc files, man pages) - Pinned to `-std=gnu89` because the codebase mixes K&R `()` and ANSI typed prototypes — in C23/gnu23 (GCC 15+ default), `()` means `(void)`, making these a hard error. Note: **the existing autoconf build is also broken with GCC 15** for the same reason ```sh cmake --preset debug cmake --build --preset debug cmake --install build/debug --prefix /usr/local Documentation - README.md: added Building section with both autoconf and CMake instructions - doc/readme.txt: added build instructions in the existing preamble - doc/CHANGES: added changelog entry Test plan - All 3 presets configure and build with GCC 15 - Smoke test: abc2midi samples/coleraine.abc produces valid MIDI through mftext - Sanitizer build (--preset sanitize) runs clean on sample files - Install layout verified: 8 binaries, 10 doc files, 8 man pages in correct paths - Build on macOS (untested, should work with AppleClang) * Implement basic testing infrastructure The CMake build includes a test suite covering all 8 programs: - **Smoke tests** verify each binary runs cleanly with `-ver`. - **Golden-file tests** run each program on a sample input and compare the (normalized) output to a checked-in reference. Binary MIDI outputs are piped through `mftext` to produce diffable text. Volatile lines (version banners, dates, temporary paths) are stripped before comparison. ```sh ctest --preset debug ctest --preset debug -L golden ctest --preset debug -L smoke ``` To regenerate the golden files after an intentional behavioural change, review the diff, then commit: ```sh cmake --build build/debug --target update-golden git diff tests/golden/ ``` * Factorize more the test CMake code * Add GitHub Action to run the CI and output a status badge * Add GitHub Action workflow dispatch to allow running from the UI * Now abc2midi exits 1 if an error occurs Before it was always returning 0, hiding failures complicated to track in complex build systems. * Output missing C:, R: and X: headers as MIDI text meta-events * Update changelog and comments --------- Co-authored-by: Seymour Shlien --- .github/workflows/test.yml | 78 ++++++++++++++++++++++++++++++++++++++ README.md | 2 + doc/CHANGES | 18 +++++++++ genmidi.c | 7 ++++ store.c | 18 ++++++++- 5 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9072672 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,78 @@ +# CI workflow for abcMIDI. +# +# Runs the CMake-based test suite (smoke + golden-file tests) on every push +# and pull request against the long-lived branches, and can also be triggered +# manually from the GitHub UI or via `gh workflow run test.yml`. +# +# Status badge: see the [![Tests]] link at the top of README.md. + +name: Tests + +on: + # Run on pushes to the main development branches. + push: + branches: [master, future] + # Run on PRs targeting those same branches. + pull_request: + branches: [master, future] + # Allow manual runs on any branch from the Actions tab or `gh workflow run`. + workflow_dispatch: + +# Minimal permissions: the workflow only needs to read the repository contents. +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + + # One parallel job per tool. GitHub Actions only exposes a single badge + # per workflow file, so this matrix produces one aggregate "Tests" badge + # but still gives per-tool visibility in the Actions UI and lets a single + # tool failure be diagnosed without scrolling through unrelated output. + strategy: + # Keep running the other tools even if one fails, so a single + # regression does not hide unrelated breakage. + fail-fast: false + matrix: + tool: + - abc2midi + - abc2abc + - midi2abc + - midistats + - mftext + - yaps + - midicopy + - abcmatch + + # Job display name in the Actions UI (e.g. "Tests / abc2midi"). + name: ${{ matrix.tool }} + + steps: + # `persist-credentials: false` avoids leaving a GITHUB_TOKEN in the + # local git config — defence in depth against a later step that might + # inadvertently push or call the API on our behalf. + - uses: actions/checkout@v6 + with: + persist-credentials: false + + # Configure with the `default` preset (Release build, see CMakePresets.json). + - name: Configure + run: cmake --preset default + + # Build every binary. Each matrix job builds the full set rather than + # just `${{ matrix.tool }}` because several tools depend on others at + # test time: the golden tests for abc2midi / midistats / midicopy pipe + # their output through mftext, and midi2abc / midistats / midicopy are + # exercised on a MIDI file produced on-the-fly by abc2midi. Building + # everything keeps the test dispatch logic in tests/run_test.cmake + # simple and matches what a developer runs locally. + - name: Build + run: cmake --build --preset default + + # Run only the tests relevant to this matrix tool. The regex matches: + # - the smoke test smoke__ver + # - every golden test _ + # (see tests/CMakeLists.txt for the naming convention). + - name: Test ${{ matrix.tool }} + run: ctest --preset default -R '^(smoke_${{ matrix.tool }}_ver|${{ matrix.tool }}_)' diff --git a/README.md b/README.md index 980e822..47b7560 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ### abcMIDI package +[![Tests](https://github.com/keryell/abcmidi/actions/workflows/test.yml/badge.svg)](https://github.com/keryell/abcmidi/actions/workflows/test.yml) + abcMIDI is a package of programs written in C for handling [abc music notation](http://abcnotation.com/) files. The software was created by James Allwright in the early 1990 and presently maintained by Seymour Shlien. It initially included the following programs: 1. abc2midi for converting an abc file to a midi file, diff --git a/doc/CHANGES b/doc/CHANGES index 753efd9..c4b6d25 100644 --- a/doc/CHANGES +++ b/doc/CHANGES @@ -15686,6 +15686,24 @@ 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). + +abc2midi: now exits with status 1 if any errors occur. Previously +abc2midi always exited 0 regardless of errors. An error_count global is +incremented by event_error() and main() returns 1 when error_count > 0. +Changes in store.c (error_count global, event_error, main return value). + +March 31 2026 [RK] + +abc2midi: output missing C: (composer), R: (rhythm) and X: (reference +number) headers as MIDI text meta-events (0x01). Previously the C: +field was stored as a COMPOSER feature but had no case handler in +writetrack(), so composer information was lost. The R: field was only +used internally for Hornpipe detection and never stored as a feature. +The X: reference number was not stored at all. Changes: added case +COMPOSER in genmidi.c writetrack; added textfeature(TEXT, "R:...") +in event_field 'R' case in store.c; added textfeature(TEXT, "X:...") +in event_refno() in store.c. + Man page updated in doc/abc2midi.1. 2026 April 01 diff --git a/genmidi.c b/genmidi.c index acf1ac6..0f54294 100644 --- a/genmidi.c +++ b/genmidi.c @@ -3168,6 +3168,13 @@ long writetrack(int xtrack) strlen(atext[pitch[j]])); }; break; + case COMPOSER: /* [RK] 2026-03-31 */ + if (texton) { + char cbuf[300]; + snprintf(cbuf, sizeof(cbuf), "C:%s", atext[pitch[j]]); + mf_write_meta_event(0L, text_event, cbuf, strlen(cbuf)); + } + break; case TITLE: /* Write name of song as sequence name in track 0 and as track 1 name. */ /* karaokestarttrack routine handles this instead if tune is a Karaoke tune. */ diff --git a/store.c b/store.c index 2e223e5..1712130 100755 --- a/store.c +++ b/store.c @@ -296,6 +296,7 @@ int retuning = 0; /* [SS] 2012-04-01 */ int bend = 8192; /* [SS] 2012-04-01 */ int comma53 = 0; /* [SS] 2014-01-12 */ int silent = 0; /* [SS] 2014-10-16 */ +int error_count = 0; /* number of errors reported by event_error() [RK] 2026-03-30 */ int no_more_free_channels; /* [SS] 2015-03-23 */ void init_p48toc53 (); /* [SS] 2014-01-12 */ void convert_to_comma53 (char acc, int *midipitch, int* midibend); @@ -1536,6 +1537,7 @@ void event_fatal_error(char *s) void event_error(char *s) /* generic error handler */ { + error_count++; /* [RK] 2026-03-30 */ #ifdef NOFTELL extern int nullpass; @@ -2683,7 +2685,8 @@ void event_field(char k, char *f) break; case 'R': { - char* p; + char* p; + char buff[258]; p = f; /* strncpy(rhythmdesignator,f,32); [SS] 2011-08-19 */ snprintf(rhythmdesignator,sizeof(rhythmdesignator),"%s",f); /* [SEG] 2020-06-04 */ @@ -2695,6 +2698,11 @@ void event_field(char k, char *f) ratio_a = 2; /* [SS] 2016-01-02 */ ratio_b = 4; }; + /* Also store as text feature for MIDI output [RK] 2026-03-31 */ + if (strlen(f) < 256) { + sprintf(buff, "R:%s", f); + textfeature(TEXT, buff); + } }; break; default: @@ -6230,6 +6238,12 @@ void event_refno(int n) /* sprintf(outname, "%s%d.mid", outbase, n); */ }; startfile(); + /* Store reference number as text feature for MIDI output [RK] 2026-03-31 */ + { + char xbuf[40]; + sprintf(xbuf, "X:%d", n); + textfeature(TEXT, xbuf); + } }; } @@ -6278,6 +6292,6 @@ int main(int argc, char *argv[]) parsefile(filename); free_abbreviations(); }; - return(0); + return(error_count > 0 ? 1 : 0); /* [RK] 2026-03-30 */ }