HeadMusic
The head_music Ruby gem provides a toolkit for working with Western music theory. Model and manipulate the fundamental elements of music including pitches, scales, key signatures, intervals, and chords.
Features
- Western Music Theory Fundamentals: Work with pitches, scales, intervals, chords, and key signatures
- Musical Analysis: Analyze harmonic progressions, voice leading, and counterpoint
- Style Analysis: Rules for species counterpoint and voice leading
- Internationalization: Support for multiple languages (English, French, German, Italian, Russian, Spanish)
- Instrument Modeling: Extensive database of musical instruments with ranges and properties
- Notation Formats: Read ABC and LilyPond into compositions; write compositions as ABC, LilyPond, and MusicXML
Installation
Add this line to your application's Gemfile:
gem 'head_music'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install head_music
Quick Start
require 'head_music'
# Work with pitches and intervals
pitch = HeadMusic::Rudiment::Pitch.get('C4')
higher_pitch = HeadMusic::Rudiment::Pitch.get('E4')
interval = HeadMusic::Analysis::DiatonicInterval.new(pitch, higher_pitch)
puts interval.name # => "major third"
# Create scales
scale = HeadMusic::Rudiment::Scale.get('C', :major)
puts scale.pitches.map(&:to_s) # => ["C4", "D4", "E4", "F4", "G4", "A4", "B4"]
# Analyze chords
pitches = %w[C4 E4 G4].map { |p| HeadMusic::Rudiment::Pitch.get(p) }
chord = HeadMusic::Analysis::PitchSet.new(pitches)
puts chord.major_triad? # => true
# Import a LilyPond excerpt as a composition
composition = HeadMusic::Notation::LilyPond.parse(<<~'LILY')
\relative c' {
\key g \major
\time 4/4
g8 a b c d c b g |
}
LILY
puts composition.voices.first.pitches.map(&:to_s) # => ["G3", "A3", "B3", "C4", "D4", "C4", "B3", "G3"]
puts composition.to_lilypond # => a complete LilyPond document
The LilyPond reader covers absolute and \relative pitches, durations and dots, rests and whole-bar rests, chords, intra-bar ties, \key, \time, \clef, bar checks, \header title and composer, and \new Staff / \new Voice contexts. Constructs outside that subset (tuplets, lyrics, variables, articulations, and so on) raise an UnsupportedFeatureError rather than being skipped.
Style Analysis
Look up a style guide by key and analyze a voice against it. Style::Guide.get returns nil for an
unknown key, so a stored key can be validated before use.
guide = HeadMusic::Style::Guide.get('first_species_harmony')
guide.category # => :harmony
guide.display_name # => "First Species Harmony"
assessment = guide.assess(voice)
assessment.fitness # => 0.0 to 1.0
assessment. # => ["Prefer contrary motion. Move voices in different melodic directions."]
A guide declares its guidelines in three tiers, and the tier decides how much each one counts:
guide.gate_items # preconditions -- can this voice be assessed at all?
guide.primary_items # what the guide is about
guide.secondary_items # background craft it inherits rather than teaches
guide.guide_items # all three, in that order
A gate asks whether the voice can be assessed at all. Failing one stops the assessment — the rubric is not computed, and the grade is the gates alone:
assessment.assessable? # => false for a voice too short to judge, or with no companion voice
assessment.fitness # => the gates' product; the rubric was never reached
Among the rules that are assessed, primaries share φ⁻¹ of the rubric and secondaries share φ⁻², which is why a species guide weighs its own rules as heavily as all the craft it inherits put together. The budgets are fixed rather than divided by item count, so what a guide teaches does not thin out as it inherits more. A rubric that declares only one tier is renormalized to the full range.
Within a tier, a second axis: strength. A prohibition (:strong, the default) weighs twice a
preference (:weak), normalized by that tier's own total. Strength never crosses a tier boundary, and
it is inert on gates, which multiply the whole rubric:
HeadMusic::Style::Guidelines::NoParallelPerfectOnDownbeats.strength # => :strong
HeadMusic::Style::Guidelines::PreferContraryMotion.strength # => :weak
Unlike tier, strength is a property of the guideline rather than of the list it was declared in — a
preference is a preference in every guide that names it. An item may override it for the
tradition-dependent case, with Guideline.with(strength: :weak).
Each entry is a Style::GuideItem — a guideline plus the configuration this guide gives it — and
assessing one yields a frozen Style::GuideItemAssessment:
item = guide.primary_items.first
item.guideline # => HeadMusic::Style::Guidelines::NoUnisonsInMiddle
item.config # => {}
item.strength # => :strong
assessment.guide_item_assessments.first.tier # => :gate
assessment.guide_item_assessments.first.strength # => :strong
assessment.guide_item_assessments.first.fitness # => 0.0 to 1.0
Guides whose tiers vary by configuration are built with .with. The six contour melodies are
registered under their own keys, and each key is exactly one such configuration:
HeadMusic::Style::Guide.get('arch_contour_melody')
# the same guide, spelled out
HeadMusic::Style::Guides::ContourMelody.with(contour: :arch, minimum_melodic_intervals: 2)
Configure it differently and you get a different guide — one the registry does not hold, whose key
is nil and whose display_name falls back to the class. Prefer the key when you mean a registered
guide, and .with when you deliberately want a configuration of your own.
Documentation
- API Documentation: rubydoc.info/gems/head_music
- Contributing Guide: CONTRIBUTING.md
- Changelog: CHANGELOG.md
Requirements
- Ruby 3.3.0 or higher
- ActiveSupport 7.0+
Development
After checking out the repo, run bin/setup to install dependencies.
Running Tests
# Run all tests
bundle exec rspec
# Run tests with coverage
bundle exec rake
# Run quality checks (tests + linting + security)
bundle exec rake quality
Code Quality
# Run linting
bundle exec rubocop
# Run security audit
bundle exec rake bundle:audit:check
# Generate documentation
bundle exec rake doc
Available Rake Tasks
rake spec- Run testsrake quality- Run tests, linting, and security auditrake doc- Generate YARD documentationrake doc_stats- Show documentation coverage statisticsrake coverage- Open coverage report in browser
Releasing a New Version
The release checklist lives in .claude/skills/release/SKILL.md. Run /release in Claude Code, or follow it by hand. In short: move the Unreleased changelog entries under a dated heading, bump lib/head_music/version.rb, refresh Gemfile.lock, commit as Release X.Y.Z, and then bundle exec rake release:source_control_push tags the release. The tag push runs the release workflow, which publishes the gem to RubyGems and creates the GitHub Release.
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Project Structure
lib/head_music/
├── analysis/ # Musical analysis tools (intervals, chords, etc.)
├── content/ # Musical content (compositions, voices, notes)
├── instruments/ # Instrument definitions and properties
├── rudiment/ # Basic music theory elements (pitches, scales, etc.)
└── style/ # Style analysis and composition rules
Code of Conduct
This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to our Code of Conduct.
License
The gem is available as open source under the terms of the MIT License.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: For security issues, please email [email protected]