Skip to content

Instantly share code, notes, and snippets.

@shaiguitar
Created January 13, 2026 08:11
Show Gist options
  • Select an option

  • Save shaiguitar/df3ebc3d6c8491fb5ed509ea4d49c63f to your computer and use it in GitHub Desktop.

Select an option

Save shaiguitar/df3ebc3d6c8491fb5ed509ea4d49c63f to your computer and use it in GitHub Desktop.

4/4 Rhythmic Resolution (Dotted 1/8 vs 1/8 Triplets)

This focuses on a single 4/4 comparison with no rests and just two staves:

  • Dotted 1/8 pattern (3/16 steps) realigns with the bar every 3 measures (ties used across barlines).
  • Eighth-note triplets (1 bar) realign within the bar and repeat each measure.

Mathematically:

  • A 4/4 bar = 16 sixteenths.
  • Dotted 1/8 = 3 sixteenths → LCM(16, 3) = 48 sixteenths = 3 measures to land back on beat 1.
  • Triplet eighths fit exactly in each bar, so they resolve every measure.

Run

python3 rhythmic_resolution_demo.py

This displays the score (music21 will use Guitar Pro if configured) and saves a single file:

  • rhythmic_resolution_4-4.mxl

Requirements

pip install music21

Educational value

  • Shows how subdivision size affects phrase alignment.
  • Contrasts a non-dividing pulse (3/16) vs a dividing pulse (triplets) in the same meter.
#!/usr/bin/env python3
"""
Demonstration of rhythmic resolution in 4/4:
- Dotted eighths (3/16) take 3 measures to realign with the bar
- Eighth-note triplets resolve cleanly every measure
We place the two patterns in parallel parts (both 4/4) to show:
- The triplet line lands back on beat 1 every bar
- The dotted-eighth line only lands back on beat 1 after 3 bars (LCM of 16 and 3 = 48 sixteenths = 3 measures)
"""
from music21 import (
stream,
meter,
note,
tempo,
metadata,
layout,
clef,
expressions,
duration,
environment,
tie,
)
def create_dotted_eighth_example_4_4():
"""
Dotted-eighth (3/16) pattern in 4/4. Realigns every 3 measures.
No rests; ties are used when a dotted eighth crosses a barline.
- Measure length: 16 sixteenths
- Step size: 3 sixteenths
- LCM(16, 3) = 48 sixteenths = 3 measures to land back on beat 1
"""
part = stream.Part()
part.insert(0, clef.TrebleClef())
part.insert(0, meter.TimeSignature("4/4"))
part.insert(0, tempo.MetronomeMark(number=96))
part.partName = "Dotted 1/8 pattern (realigns in 3 bars)"
pitches = ["C4", "D4"] # simple two-note cell
dotted_q_len = 0.75 # dotted eighth in quarter-length units
measures = [stream.Measure(number=i + 1) for i in range(3)]
measure_idx = 0
remaining_in_measure = 4.0
pending_tie_stop = False
for i in range(16): # 16 dotted eighths = 3 measures exactly
pitch = pitches[i % len(pitches)]
dur_left = dotted_q_len
while dur_left > 0 and measure_idx < len(measures):
available = remaining_in_measure
this_len = min(dur_left, available)
n = note.Note(pitch)
n.duration.quarterLength = this_len
# Manage ties across barlines
if pending_tie_stop and dur_left == dotted_q_len:
n.tie = tie.Tie("stop")
pending_tie_stop = False
if dur_left > available:
# Will continue into next bar
n.tie = tie.Tie("start")
pending_tie_stop = True
measures[measure_idx].append(n)
dur_left -= this_len
remaining_in_measure -= this_len
if remaining_in_measure <= 1e-9:
measure_idx += 1
if measure_idx < len(measures):
remaining_in_measure = 4.0
# Annotations
if measures:
start_text = expressions.TextExpression("Starts on beat 1")
start_text.style.absoluteY = 20
measures[0].insert(0, start_text)
if len(measures) >= 3:
resolve_text = expressions.TextExpression("Realigns on beat 1 (after 3 bars)")
resolve_text.style.absoluteY = 20
measures[2].insert(0, resolve_text)
for m in measures:
part.append(m)
return part
def create_triplet_example_4_4(measures: int = 1, name: str = "1/8 triplets"):
"""
Eighth-note triplets in 4/4. Realigns every bar.
measures: number of bars to illustrate repeated resolution.
"""
part = stream.Part()
part.insert(0, clef.TrebleClef())
part.insert(0, meter.TimeSignature("4/4"))
part.insert(0, tempo.MetronomeMark(number=96))
part.partName = name
part.partAbbreviation = name
pitches = ["C4", "D4", "E4"]
for m_idx in range(measures):
measure = stream.Measure(number=m_idx + 1)
# 4 groups of triplets fill a 4/4 measure
for _ in range(4):
for pitch in pitches:
n = note.Note(pitch)
n.duration.type = "eighth"
n.duration.tuplets = [duration.Tuplet(3, 2)]
measure.append(n)
if m_idx == 0:
text_exp = expressions.TextExpression("Realigns every bar")
text_exp.style.absoluteY = 20
measure.insert(0, text_exp)
part.append(measure)
return part
def create_score_4_4():
"""
Single score comparing two 4/4 parts:
- Upper staff: dotted eighth pattern (realigns every 3 bars)
- Lower staff: triplet pattern over 1 bar (realigns every bar)
"""
score = stream.Score()
score.metadata = metadata.Metadata()
score.metadata.title = "4/4 Rhythmic Resolution"
score.metadata.composer = "Educational Example"
score.metadata.subtitle = "Dotted 1/8 (3-bar cycle) vs 1/8 triplets (1-bar cycle)"
dotted_part = create_dotted_eighth_example_4_4()
triplet_part_single = create_triplet_example_4_4(measures=1, name="Triplets (1 bar)")
score.insert(
0,
layout.StaffGroup(
[dotted_part, triplet_part_single],
name="Dotted vs Triplets",
abbreviation="Dots vs Trips",
),
)
score.append(dotted_part)
score.append(triplet_part_single)
return score
def main():
"""
Generate and display the musical examples.
"""
# Point music21 to Guitar Pro 8 for opening MusicXML if available
env = environment.UserSettings()
gp_path = "/Applications/Guitar Pro 8.app/Contents/MacOS/GuitarPro"
try:
env["musicxmlPath"] = gp_path
except Exception as exc:
# If setting fails, continue; user can open the generated MXL files manually
print(f"Warning: could not set musicxmlPath to Guitar Pro 8 ({exc})")
print("=" * 70)
print("4/4 Rhythmic Resolution Demo")
print("=" * 70)
print()
print("This shows two simultaneous 4/4 patterns:")
print(" - Dotted 1/8 pattern (3/16 step) realigns every 3 bars")
print(" - 1/8 triplets realign every bar")
print()
score = create_score_4_4()
# Show the score
print("Displaying score in music21 (uses Guitar Pro if available)...")
score.show("musicxml")
# Save single output
output_dir = __import__("pathlib").Path(__file__).parent
score_path = output_dir / "rhythmic_resolution_4-4.mxl"
print(f"\nSaving score to: {score_path}")
score.write("musicxml", score_path)
print("\n✓ Done! One file generated. Open in your notation software if viewer not configured.")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment