Skip to content

Counterpoint Course ​

MIDI Sketch Bach does not treat counterpoint as decorative theory text. Each form builder authors the musical lines, the composer replays those lines through material carriers, and the validator checks the result against named rule IDs. Scored note search is separate and off by default; --free-counterpoint enables it only for the Passacaglia counterline. This course teaches the constraints as small, checkable contracts, each shown on staff notation you can also listen to.

Start here if the words are new

The course defines each rule locally, but it assumes the basic nouns voice, bar, chord, interval, fifth, cadence, and metric accent. The short foundation — including how to read the staff figures and how meter assigns strong, medium, and weak positions — is Music Primer for Engineers.

Why a course, not a rule list ​

A rule ID such as parallel_fifth is the end of a chain of musical reasoning. Reading the chain backwards — "this is forbidden because the voices fuse, and voices fusing matters because counterpoint is the art of simultaneous independent lines" — is what makes a validation report readable instead of arbitrary. The course walks that chain forward:

ChapterWhat you learnRules covered
1. Intervals & ConsonanceHow two simultaneous notes are classified: perfect, imperfect, dissonant, and the ambivalent fourthfoundation for all vertical rules
2. Motion & Forbidden ParallelsThe four types of relative motion; strict and contrary perfect-motion checks; hidden perfects, crossing, spacing, and invertible counterpointparallel_fifth, parallel_octave, anti_parallel_perfect, battuta, hidden_parallel_fifth, hidden_parallel_octave, voice_crossing, spacing_adjacent_voices_within_octave, invertible_at_octave
3. Dissonance TreatmentStructural accents demand chord tones; weak positions tolerate passing and neighbor tones; the four suspension figuresstrong_beat_dissonance, vertical_dissonance, unprepared_dissonance, suspension_preparation, suspension_preparation_duration, suspension_metrical_accent, suspension_resolution_step_down, suspension_interval, suspension_seventh_sixth
4. Melodic WritingEach voice judged as a line: forbidden leaps, leap recovery, the leading tone's obligationaugmented_melodic, diminished_melodic, tritone_melodic, consecutive_leaps, leading_tone_resolution, voice_range_integrity
5. Tonal GrammarThe seven cadence types, tendency-tone doubling, cross relations, applied dominants, pivot modulationcadence_voice_leading, doubling_no_leading_tone, doubling_no_seventh, cross_relation, secondary_dominant_resolution, modulation_pivot_chord_required
6. Fugal DevicesSubject and answer, countersubject, episodes and sequences, imitation, stretto, pedal pointstonal_answer_dominant_mapping, countersubject_continuous, countersubject_invertible, episode_motif_derived, sequence_pattern_consistency, imitation_entry_match, imitation_entry_realization, middle_entry_in_related_key, stretto_overlap_valid, pedal_point_tonic_or_dominant
7. Form-Specific ConstraintsImmutable grounds and cantus firmi, figuration harmony, implied voices in solo strings, phrase grids, texture rulesground_bass_immutable, passacaglia_ground_immutable, cantus_firmus_immutable, variation_role_ornament_constraint, figuration_harmonic_consistency, toccata_archetype_compatible, implicit_voice_counterpoint, arpeggio_no_parallel_perfect, phrase_periodicity_4_or_8_bar, anacrusis_consistent, pedal_range_soft_penalty, voice_independence_threshold, section_contrast_required

For the flat, debugging-oriented index of every rule ID, see the Validator Rule Reference.

Reading the examples ​

Every chapter teaches with staff examples rendered by the same component. Most are synthetic: like a unit test, each isolates one rule in the smallest musical situation — two or three staves — that triggers (or legally avoids) it. Examples wearing the From Bach badge are different: they quote real passages from the literature (the Well-Tempered Clavier, the Goldberg Variations, the organ and solo-string works), and every quoted note is verified against the source before it appears here. The synthetic figures show a rule at its smallest; the quotes show what that rule protects in real music.

ForbiddenParallel fifths
The red verticals show the repeated perfect fifth. The arrows show that neither voice is stationary, so this is not oblique motion.
parallel_fifthBoth voices move up, and the red vertical interval remains P5 at both arrivals.

If you do not read notation yet

Read the staff examples as diagrams. A voice is one independent line, like one stream in a concurrent program. The upper staff is the higher line; the lower staff is the lower line. Colored labels, rings, boxes, and arrows show the exact place the validator is explaining; they are analysis overlays, not extra musical notes.

How to inspect an example:

  1. Read the title and the rule chips (the red monospace tags are validator rule IDs).
  2. Press the play button in the header to hear it — the sounding notes light up on the staff as it plays, so the eye and the ear stay synchronized. Forbidden patterns are usually audible: parallel octaves really do sound like one voice.
  3. Find the colored overlay. Red marks a violation, amber marks a scoped or conditionally allowed sonority, green marks the correct resolution.
  4. For motion rules, compare the previous note pair with the current note pair. For dissonance rules, ask whether the marked note belongs to the chord and whether it resolves by step.
  5. Comparison examples are marked voices play in turn in the header: the staves sound one after the other instead of together (a subject and its inversion, a good and a bad version). Some figures also carry a row of pill buttons — alternative excerpts of the same figure, such as another Goldberg variation over the same ground — and switching pills redraws the score.

How the engine applies these rules ​

The public generation path is bach_generate_from_json: parse config, resolve the form and bar count, build a form fixture, run the composer, validate the generated score, apply the ornament pass, validate the FinalScore, and apply the form's counterpoint budget. For a passing score it applies the velocity curve, snapshots the notated notes, applies articulation, re-renders the tracks, assigns instruments, adds CC and tempo events, and exports MIDI and event JSON. Validation reports and the successful generated.v1 audit retain the notated lengths used for validation; tracks, MIDI, and public event data use the lengths heard in performance after articulation. Counterpoint validation lives in ../midi-sketch-bach/src/composer/validator.cpp; material declarations live in material.h.

The validator separates local sonority from source ownership:

TermMeaning in the engine
Compose noteA note produced by scored search. No default form uses this path; --free-counterpoint routes only the Passacaglia counterline through it.
Material noteAn authored subject, answer, ground bass, cantus firmus, variation, or other carrier payload replayed verbatim. This is the default generation path.
Immutable carrierGround bass, passacaglia ground, or the bar-head skeleton of a cantus firmus. Ground carriers are exempt from the ornament pass; cantus-firmus bar heads stay plain while eligible within-bar notes can still receive decoration.
Structural accentThe downbeat plus meter-specific medium accents: beat 3 in 4/4, dotted pulses in compound meter, beat 2 in a Sarabande, and the midpoint of longer even simple meters (primer).
Weak positionA position outside that accent grid, where prepared non-chord tones can function as passing or ornamental tones.

For the counterpoint observations routed through the recorder, Generation records a finding as an exemption when all operands are fixed Material or Ornament notes because that pass cannot rewrite its inputs. FinalScore evaluates authored material again: an all-authored observation is retained as informational evidence, while an observation with a generated side can remain a failure. After that audit, applyCounterpointBudget turns observed vertical findings into a form-level failure for closed rules; linear observations remain measured. Direct structural and integrity checks have their own failure paths. Thus a fixed-material match is not globally erased, and the final status depends on the form's measured vertical budget.

Failure kinds ​

Every reported failure carries a FailKind that tells you which layer broke:

KindMeaningRules that use it
MusicalFailA counterpoint or harmony contract was violated. The default for almost all rules.parallels, dissonance, melodic, doubling, fugal, phrase, and texture rules
StructuralFailThe form's structural promise was broken.ground_bass_immutable, passacaglia_ground_immutable, goldberg_aria_bass_immutable, cantus_firmus_immutable; also cadence_voice_leading when the cadence layout itself is malformed
ConfigFailThe request itself was invalid (reported before composition).configuration validation, not counterpoint rules

Practical mental model

The form builder authors the musical object and material carriers replay it verbatim. The validator then accepts or rejects that assembled result. Scored search enters only when --free-counterpoint reroutes the Passacaglia's inner counterline.

Reading a validation report ​

Generation validation runs after carrier assembly, and a blocking generation violation aborts the public request; the composer does not repair the span or emit a fallback note. The ornament pass then runs a FinalScore audit, followed by the form-specific vertical budget, so a post-ornament failure can also abort before output. Diagnostics therefore come in two shapes: the failed-run report, and the per-note provenance audit trail for a successful piece.

1. The failure lines. A hard failure prints one line per violation (native CLI, exit code 3):

Composer validation failed: 2 rule violations
  - parallel_fifth (span 17)
  - strong_beat_dissonance (span 17)

Each line is a rule ID — the tokens this course teaches, indexed in the Validator Rule Reference — plus the span where it fired.

2. What a span is. A span is one voice's slice of one time region. Span boundaries follow the harmonic plan: cadence anchors, subject entries, and phrase joins all start a new span. So "span 17" is not an abstract counter — it names this voice, these bars.

3. From span to bars. With --generated-json -o piece.mid, the CLI writes two index-parallel files: piece.generated.json (generated.v1 — tick, pitch, voice, velocity per note) and piece.provenance.json (provenance.v1 — span, source, scores per note). Two queries connect them:

bash
# which note indexes belong to span 17?
jq '[.notes[] | select(.span_id == 17) | .index]' piece.provenance.json

# where does note 42 sit on the timeline?
jq '.notes[42] | {start_tick, pitch, voice}' piece.generated.json

With 480 ticks per quarter (the file's ticks_per_beat field) and four beats to the bar, start_tick / 1920 is the zero-based bar number: a note at tick 26880 opens bar 15. It is a structural accent because it is the downbeat; in 4/4, beat 3 is another structural accent checked by strong_beat_dissonance (chapter 3).

4. Read the chain backwards. From here the course is the decoder. parallel_fifth (chapter 2): compare the flagged pair with the previous pair — both voices moved in the same direction and both vertical intervals were perfect fifths — then check the cadence and provenance scope. anti_parallel_perfect and battuta are separate contrary-motion checks. strong_beat_dissonance (chapter 3): is the note at that structural accent a chord tone of the declared harmony? Every chapter ends with the same table — rule, FailKind, exemptions — for precisely this lookup.

5. The audit trail on success. Every note's provenance row records source (Material / Compose / Ornament), scoring fields, and satisfied_rules, the rule-bit mask stamped on the note. Default carrier replay produces Material rows; candidate scores and rejected alternatives describe scored selection only on the opt-in Passacaglia counterline. Fixed-source pair findings are recorded as generation-time exemptions, then the full lifecycle — including FinalScore and the form budget — remains visible in the diagnostic validation result.

The web demo and the full trail

The JavaScript/WASM library exposes getDiagnostic(), getGenerated(), and getProvenance() in addition to getEvents(). This homepage's UI wrapper currently consumes only events. Use the library methods for failed-run diagnostics and the two index-parallel audit objects, or use the native CLI sidecars shown above.

Continue with Chapter 1 — Intervals & Consonance.

Dual-licensed: AGPL-3.0 · commercial licensing available. Generated MIDI is yours to use freely.