head_music 8.3.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -3
  3. data/CHANGELOG.md +71 -0
  4. data/CLAUDE.md +62 -25
  5. data/Gemfile +7 -1
  6. data/Gemfile.lock +91 -3
  7. data/MUSIC_THEORY.md +120 -0
  8. data/README.md +18 -0
  9. data/Rakefile +7 -2
  10. data/head_music.gemspec +1 -1
  11. data/lib/head_music/analysis/diatonic_interval.rb +29 -27
  12. data/lib/head_music/analysis/dyad.rb +229 -0
  13. data/lib/head_music/analysis/interval_consonance.rb +51 -0
  14. data/lib/head_music/analysis/melodic_interval.rb +1 -1
  15. data/lib/head_music/analysis/pitch_class_set.rb +111 -14
  16. data/lib/head_music/analysis/{pitch_set.rb → pitch_collection.rb} +11 -5
  17. data/lib/head_music/analysis/sonority.rb +50 -12
  18. data/lib/head_music/content/note.rb +1 -1
  19. data/lib/head_music/content/placement.rb +1 -1
  20. data/lib/head_music/content/position.rb +1 -1
  21. data/lib/head_music/content/voice.rb +1 -1
  22. data/lib/head_music/instruments/alternate_tuning.rb +102 -0
  23. data/lib/head_music/instruments/alternate_tunings.yml +78 -0
  24. data/lib/head_music/instruments/instrument.rb +231 -72
  25. data/lib/head_music/instruments/instrument_configuration.rb +66 -0
  26. data/lib/head_music/instruments/instrument_configuration_option.rb +38 -0
  27. data/lib/head_music/instruments/instrument_configurations.yml +288 -0
  28. data/lib/head_music/instruments/instrument_families.yml +77 -0
  29. data/lib/head_music/instruments/instrument_family.rb +15 -5
  30. data/lib/head_music/instruments/instruments.yml +795 -965
  31. data/lib/head_music/instruments/playing_technique.rb +75 -0
  32. data/lib/head_music/instruments/playing_techniques.yml +826 -0
  33. data/lib/head_music/instruments/score_order.rb +136 -0
  34. data/lib/head_music/instruments/score_orders.yml +130 -0
  35. data/lib/head_music/instruments/staff.rb +61 -1
  36. data/lib/head_music/instruments/staff_scheme.rb +6 -4
  37. data/lib/head_music/instruments/stringing.rb +115 -0
  38. data/lib/head_music/instruments/stringing_course.rb +58 -0
  39. data/lib/head_music/instruments/stringings.yml +168 -0
  40. data/lib/head_music/instruments/variant.rb +6 -1
  41. data/lib/head_music/locales/de.yml +29 -0
  42. data/lib/head_music/locales/en.yml +106 -0
  43. data/lib/head_music/locales/es.yml +29 -0
  44. data/lib/head_music/locales/fr.yml +29 -0
  45. data/lib/head_music/locales/it.yml +29 -0
  46. data/lib/head_music/locales/ru.yml +29 -0
  47. data/lib/head_music/{rudiment → notation}/musical_symbol.rb +3 -3
  48. data/lib/head_music/notation/staff_mapping.rb +70 -0
  49. data/lib/head_music/notation/staff_position.rb +62 -0
  50. data/lib/head_music/notation.rb +7 -0
  51. data/lib/head_music/rudiment/alteration.rb +34 -49
  52. data/lib/head_music/rudiment/alterations.yml +32 -0
  53. data/lib/head_music/rudiment/base.rb +9 -0
  54. data/lib/head_music/rudiment/chromatic_interval.rb +4 -7
  55. data/lib/head_music/rudiment/clef.rb +2 -2
  56. data/lib/head_music/rudiment/consonance.rb +39 -5
  57. data/lib/head_music/rudiment/diatonic_context.rb +25 -0
  58. data/lib/head_music/rudiment/key.rb +77 -0
  59. data/lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb +1 -1
  60. data/lib/head_music/rudiment/key_signature.rb +21 -8
  61. data/lib/head_music/rudiment/letter_name.rb +3 -3
  62. data/lib/head_music/rudiment/meter.rb +19 -9
  63. data/lib/head_music/rudiment/mode.rb +92 -0
  64. data/lib/head_music/rudiment/note.rb +112 -0
  65. data/lib/head_music/rudiment/pitch/parser.rb +52 -0
  66. data/lib/head_music/rudiment/pitch.rb +5 -6
  67. data/lib/head_music/rudiment/pitch_class.rb +1 -1
  68. data/lib/head_music/rudiment/quality.rb +1 -1
  69. data/lib/head_music/rudiment/reference_pitch.rb +1 -1
  70. data/lib/head_music/rudiment/register.rb +4 -1
  71. data/lib/head_music/rudiment/rest.rb +36 -0
  72. data/lib/head_music/rudiment/rhythmic_element.rb +53 -0
  73. data/lib/head_music/rudiment/rhythmic_unit/parser.rb +86 -0
  74. data/lib/head_music/rudiment/rhythmic_unit.rb +13 -5
  75. data/lib/head_music/rudiment/rhythmic_units.yml +80 -0
  76. data/lib/head_music/rudiment/rhythmic_value/parser.rb +77 -0
  77. data/lib/head_music/{content → rudiment}/rhythmic_value.rb +23 -5
  78. data/lib/head_music/rudiment/scale.rb +4 -5
  79. data/lib/head_music/rudiment/scale_degree.rb +1 -1
  80. data/lib/head_music/rudiment/scale_type.rb +9 -3
  81. data/lib/head_music/rudiment/solmization.rb +1 -1
  82. data/lib/head_music/rudiment/spelling.rb +8 -4
  83. data/lib/head_music/rudiment/tempo.rb +85 -0
  84. data/lib/head_music/rudiment/tonal_context.rb +35 -0
  85. data/lib/head_music/rudiment/tuning/just_intonation.rb +0 -39
  86. data/lib/head_music/rudiment/tuning/meantone.rb +0 -39
  87. data/lib/head_music/rudiment/tuning/pythagorean.rb +0 -39
  88. data/lib/head_music/rudiment/tuning.rb +21 -1
  89. data/lib/head_music/rudiment/unpitched_note.rb +62 -0
  90. data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
  91. data/lib/head_music/style/medieval_tradition.rb +26 -0
  92. data/lib/head_music/style/modern_tradition.rb +31 -0
  93. data/lib/head_music/style/renaissance_tradition.rb +26 -0
  94. data/lib/head_music/style/tradition.rb +21 -0
  95. data/lib/head_music/time/clock_position.rb +84 -0
  96. data/lib/head_music/time/conductor.rb +264 -0
  97. data/lib/head_music/time/meter_event.rb +37 -0
  98. data/lib/head_music/time/meter_map.rb +173 -0
  99. data/lib/head_music/time/musical_position.rb +188 -0
  100. data/lib/head_music/time/smpte_timecode.rb +164 -0
  101. data/lib/head_music/time/tempo_event.rb +40 -0
  102. data/lib/head_music/time/tempo_map.rb +187 -0
  103. data/lib/head_music/time.rb +32 -0
  104. data/lib/head_music/utilities/case.rb +27 -0
  105. data/lib/head_music/utilities/hash_key.rb +34 -2
  106. data/lib/head_music/version.rb +1 -1
  107. data/lib/head_music.rb +71 -22
  108. data/user_stories/active/string-pitches.md +41 -0
  109. data/user_stories/backlog/notation-style.md +183 -0
  110. data/user_stories/backlog/organizing-content.md +80 -0
  111. data/user_stories/done/consonance-dissonance-classification.md +117 -0
  112. data/user_stories/{backlog → done}/dyad-analysis.md +6 -16
  113. data/user_stories/done/epic--score-order/PLAN.md +244 -0
  114. data/user_stories/done/expand-playing-techniques.md +38 -0
  115. data/user_stories/done/handle-time.md +7 -0
  116. data/user_stories/done/handle-time.rb +163 -0
  117. data/user_stories/done/instrument-architecture.md +238 -0
  118. data/user_stories/done/instrument-variant.md +65 -0
  119. data/user_stories/done/move-musical-symbol-to-notation.md +161 -0
  120. data/user_stories/done/move-staff-mapping-to-notation.md +158 -0
  121. data/user_stories/done/move-staff-position-to-notation.md +141 -0
  122. data/user_stories/done/notation-module-foundation.md +102 -0
  123. data/user_stories/done/percussion_set.md +260 -0
  124. data/user_stories/done/sonority-identification.md +37 -0
  125. data/user_stories/done/superclass-for-note.md +30 -0
  126. data/user_stories/epics/notation-module.md +135 -0
  127. data/user_stories/visioning/agentic-daw.md +2 -0
  128. metadata +84 -18
  129. data/TODO.md +0 -109
  130. data/check_instrument_consistency.rb +0 -0
  131. data/test_translations.rb +0 -15
  132. data/user_stories/backlog/consonance-dissonance-classification.md +0 -57
  133. data/user_stories/backlog/pitch-set-classification.md +0 -62
  134. data/user_stories/backlog/sonority-identification.md +0 -47
  135. /data/user_stories/{backlog → done/epic--score-order}/band-score-order.md +0 -0
  136. /data/user_stories/{backlog → done/epic--score-order}/chamber-ensemble-score-order.md +0 -0
  137. /data/user_stories/{backlog → done/epic--score-order}/orchestral-score-order.md +0 -0
  138. /data/user_stories/{backlog → done}/pitch-class-set-analysis.md +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec01f26a90e391dbe250ca2587586ceb9bc062a8eba06d97f52767eaf08bb2f0
4
- data.tar.gz: b2253758a487b28f9e02ae6479da2586da39ff45337bca0a24f9634988ff9eb4
3
+ metadata.gz: c3abf9e12840eb88aee1cb454116cbd092f224eb1961cfda5567444ad9e91d2f
4
+ data.tar.gz: e7680154e971414788fc8bd1a87f53111bc82c9452f4df5a8ad47a74b1c5c16d
5
5
  SHA512:
6
- metadata.gz: 6633eb9a47b72f81a44225d475d60b67924136809d469ba2a45ffe39ea6c1356ffbecd79e64f931c15146dd00980a9ff220c2b5c95deb955e5b3cf0eec7fef67
7
- data.tar.gz: 5628ee5c9f0a442560f502d071f155e0d93771b3081572e9a98a86a154f387b0c9431662b051fd58bb9189b71906b5e7c9c29dbeddebd278f1d4a23d97416634
6
+ metadata.gz: 199c8cdba49e5d6e15e7047a8bb8ee28eaa5c8087c9f427916d087d4ce8986f04cf7dd354666247b389836256230d5c3535ff5fd79964443626e397c0cdb3288
7
+ data.tar.gz: d793d6730a790b088fcc7906bc6cb02b865364852767534c15c1b76b40622ae6a12b23a2bde5f651687e9ce124941501ae8d1f17f9a63a7c8c473a2112914024
@@ -12,7 +12,8 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby-version: ['3.3.0', '3.3', '3.4']
15
+ ruby-version: ['3.3', '3.4']
16
+ activesupport-version: ['7.2', '8.0']
16
17
 
17
18
  steps:
18
19
  - uses: actions/checkout@v4
@@ -21,13 +22,18 @@ jobs:
21
22
  uses: ruby/setup-ruby@v1
22
23
  with:
23
24
  ruby-version: ${{ matrix.ruby-version }}
24
- bundler-cache: true # runs 'bundle install' and caches installed gems
25
+ bundler-cache: false
26
+
27
+ - name: Install dependencies with ActiveSupport ${{ matrix.activesupport-version }}
28
+ run: |
29
+ bundle config set --local path vendor/bundle
30
+ ACTIVESUPPORT_VERSION="${{ matrix.activesupport-version }}" bundle install
25
31
 
26
32
  - name: Run tests
27
33
  run: bundle exec rspec
28
34
 
29
35
  - name: Upload coverage to Codecov
30
- if: matrix.ruby-version == '3.3.0'
36
+ if: matrix.ruby-version == '3.3' && matrix.activesupport-version == '8.0'
31
37
  uses: codecov/codecov-action@v5
32
38
  with:
33
39
  token: ${{ secrets.CODECOV_TOKEN }}
data/CHANGELOG.md CHANGED
@@ -7,6 +7,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [11.0.0] - 2026-01-05
11
+
12
+ ### Changed
13
+ - **BREAKING**: Widened ActiveSupport dependency from `~> 7.0` to `>= 7.0, < 10` to support Rails 8.x
14
+ - Improved I18n initialization to be non-destructive:
15
+ - No longer overwrites `I18n.default_locale` (allows Rails apps to control their default)
16
+ - Adds HeadMusic locales to `available_locales` instead of replacing them
17
+ - Only sets fallbacks if not already configured by the application
18
+ - Updated CI workflow to test against both ActiveSupport 7.x and 8.x
19
+
20
+ ### Fixed
21
+ - Fixed compatibility issue with Rails 8.1.x applications
22
+
23
+ ## [10.0.0] - 2025-12-01
24
+
25
+ ### Changed
26
+ - Internal release for testing
27
+
28
+ ## [9.0.0] - 2025-10-24
29
+
30
+ ### Added
31
+ - Added `HeadMusic::Rudiment::Pitch::Parser` for strict pitch parsing
32
+ - Added `HeadMusic::Rudiment::RhythmicValue::Parser` for rhythmic value parsing
33
+ - Both parsers provide standardized `.parse()` class method API
34
+
35
+ ### Changed
36
+ - `Pitch.from_name` now uses `Pitch::Parser` internally
37
+ - `RhythmicValue.get` now uses `RhythmicValue::Parser` internally
38
+ - `Note.get` now parses "pitch rhythmic_value" strings inline without Parse module
39
+
40
+ ### Removed
41
+ - **BREAKING**: Removed `HeadMusic::Parse::Pitch` class
42
+ - **BREAKING**: Removed `HeadMusic::Parse::RhythmicValue` class
43
+ - **BREAKING**: Removed `HeadMusic::Parse::RhythmicElement` class
44
+ - **BREAKING**: Removed entire `HeadMusic::Parse` module
45
+
46
+ ### Migration Guide
47
+
48
+ If you were using the removed Parse classes, migrate as follows:
49
+
50
+ ```ruby
51
+ # Before (v8.x)
52
+ parser = HeadMusic::Parse::Pitch.new("C#4")
53
+ pitch = parser.pitch
54
+
55
+ # After (v9.x)
56
+ pitch = HeadMusic::Rudiment::Pitch.get("C#4")
57
+ # or for strict parsing:
58
+ pitch = HeadMusic::Rudiment::Pitch::Parser.parse("C#4")
59
+ ```
60
+
61
+ ```ruby
62
+ # Before (v8.x)
63
+ parser = HeadMusic::Parse::RhythmicValue.new("dotted quarter")
64
+ value = parser.rhythmic_value
65
+
66
+ # After (v9.x)
67
+ value = HeadMusic::Rudiment::RhythmicValue.get("dotted quarter")
68
+ # or for strict parsing:
69
+ value = HeadMusic::Rudiment::RhythmicValue::Parser.parse("dotted quarter")
70
+ ```
71
+
72
+ ```ruby
73
+ # Before (v8.x)
74
+ parser = HeadMusic::Parse::RhythmicElement.new("F#4 dotted-quarter")
75
+ note = parser.note
76
+
77
+ # After (v9.x)
78
+ note = HeadMusic::Rudiment::Note.get("F#4 dotted-quarter")
79
+ ```
80
+
10
81
  ## [8.2.1] - 2025-06-21
11
82
 
12
83
  ### Added
data/CLAUDE.md CHANGED
@@ -41,14 +41,26 @@ bundle exec rake doc
41
41
  bundle exec rake doc_stats
42
42
  ```
43
43
 
44
- ### Linting and Formatting
44
+ ### Git Etiquette
45
45
 
46
- The project uses Standard Ruby for style enforcement. Always run linting before committing:
46
+ **IMPORTANT: Do not make a commit unless I explicitly ask you to.** Wait for explicit instruction before running `git commit`.
47
+
48
+ When composing git commit messages, follow best-practices. However, do not mention yourself (claude) or list yourself as a co-author.
49
+
50
+ This project uses a rebase flow and `main` as the mainline branch.
51
+
52
+ ### Code Style, Linting, and Formatting
53
+
54
+ The project uses Standard Ruby for style enforcement. Always run linting after editing and before committing.
47
55
 
48
56
  ```bash
49
- bundle exec rubocop
57
+ bundle exec rubocop -a
50
58
  ```
51
59
 
60
+ Always strip trailing whitespace from all lines being added or edited. Always include a blank line at the end of each file.
61
+
62
+ Do not use an assignment inside a condition.
63
+
52
64
  ### Testing
53
65
 
54
66
  Tests are written in RSpec and located in the `/spec` directory, mirroring the `/lib` structure. The project requires 90% code coverage minimum.
@@ -60,25 +72,38 @@ Tests are written in RSpec and located in the `/spec` directory, mirroring the `
60
72
  The codebase follows a domain-driven design with clear module boundaries:
61
73
 
62
74
  1. **HeadMusic::Rudiment** - Core music theory elements
63
- - Pitch, Note, Scale, Key, Interval, Chord
64
- - Factory methods: `.get()` for most rudiments
65
-
66
- 2. **HeadMusic::Analysis** - Musical analysis tools
67
- - Intervals, Chords, Motion analysis
68
- - Harmonic and melodic analysis
69
-
70
- 3. **HeadMusic::Content** - Musical composition representation
71
- - Composition, Voice, Note, Rest
72
- - Rhythmic values and time signatures
73
-
74
- 4. **HeadMusic::Instruments** - Instrument definitions
75
- - Instrument families and properties
76
- - Pitch ranges and transposition
77
-
78
- 5. **HeadMusic::Style** - Composition rules and guidelines
79
- - Counterpoint rules
80
- - Voice leading guidelines
81
- - Style analysis
75
+ - Abstract concepts: pitch, interval, scale, chord, key
76
+ - Duration concepts (without visual representation)
77
+ - Factory methods: `.get()` for most rudiments
78
+
79
+ 2. **HeadMusic::Notation** - Visual music notation and representation
80
+ - Staff positions, lines, spaces, ledger lines
81
+ - Musical symbols (ASCII, Unicode, HTML entities)
82
+ - Clef placement and rendering
83
+ - Notehead shapes, stems, flags, beams
84
+ - Accidental placement rules
85
+ - Future: ties, slurs, articulations, dynamics
86
+
87
+ 3. **HeadMusic::Instruments** - Instrument definitions
88
+ - Instrument families and classification
89
+ - Pitch ranges and transposition
90
+ - Playing techniques
91
+ - Score ordering
92
+
93
+ 4. **HeadMusic::Content** - Musical composition representation
94
+ - Compositions, voices, bars, positions
95
+ - Notes in context (pitch + duration + placement)
96
+ - Temporal organization
97
+
98
+ 5. **HeadMusic::Analysis** - Musical analysis tools
99
+ - Intervals, chords, motion analysis
100
+ - Harmonic and melodic analysis
101
+ - Pitch class sets and collections
102
+
103
+ 6. **HeadMusic::Style** - Composition rules and guidelines
104
+ - Counterpoint rules
105
+ - Voice leading guidelines
106
+ - Style analysis
82
107
 
83
108
  ### Key Design Patterns
84
109
 
@@ -109,12 +134,21 @@ The gem supports multiple languages through the HeadMusic::Named mixin:
109
134
  - Shared examples in `spec/support/`
110
135
  - `composition_context.rb` provides test utilities
111
136
 
137
+ ### Documentation Philosophy
138
+
139
+ This project deliberately deprioritizes formal documentation in favor of clear, comprehensive tests.
140
+
141
+ - **Tests serve as documentation**: RSpec specs demonstrate how to use the code
142
+ - **Comments explain "why", not "what"**: Only add comments when the implementation is surprising or non-obvious
143
+ - **No YARD documentation required**: The code should be self-explanatory through clear naming and test examples
144
+ - Documentation tools like `rake doc` and `rake doc_stats` exist but low coverage is intentional
145
+
112
146
  ### Code Style
113
147
 
114
148
  - Ruby 3.3.0+ features are allowed
115
149
  - Follow Standard Ruby style guide
116
- - Use YARD documentation format for public methods
117
150
  - Prefer delegation over inheritance
151
+ - Always run `bundle exec rubocop -a` after editing ruby code
118
152
 
119
153
  ## Common Development Tasks
120
154
 
@@ -130,5 +164,8 @@ The gem supports multiple languages through the HeadMusic::Named mixin:
130
164
 
131
165
  1. Check for dependent classes that might be affected
132
166
  2. Run tests for the specific module: `bundle exec rspec spec/head_music/[module_name]`
133
- 3. Update documentation with YARD comments
134
- 4. Ensure translations are updated if names change
167
+ 3. Ensure translations are updated if names change
168
+
169
+ ## Music theory and concepts
170
+
171
+ Please refer to MUSIC_THEORY.md for music theory domain knowledge.
data/Gemfile CHANGED
@@ -1,10 +1,15 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- ruby "3.3.0"
3
+ ruby ">= 3.3.0"
4
4
 
5
5
  # Specify your gem's dependencies in head_music.gemspec
6
6
  gemspec
7
7
 
8
+ # Allow CI to test against specific ActiveSupport versions
9
+ if ENV["ACTIVESUPPORT_VERSION"]
10
+ gem "activesupport", "~> #{ENV["ACTIVESUPPORT_VERSION"]}.0"
11
+ end
12
+
8
13
  gem "standard", require: false
9
14
 
10
15
  group :test do
@@ -16,6 +21,7 @@ end
16
21
 
17
22
  group :development do
18
23
  gem "bundler-audit", require: false
24
+ gem "rubycritic", require: false
19
25
  gem "yard", require: false
20
26
  gem "kramdown", require: false
21
27
  end
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- head_music (8.3.0)
5
- activesupport (~> 7.0)
4
+ head_music (11.0.0)
5
+ activesupport (>= 7.0, < 10)
6
6
  humanize (~> 2.0)
7
7
  i18n (~> 1.8)
8
8
 
@@ -21,25 +21,81 @@ GEM
21
21
  minitest (>= 5.1)
22
22
  securerandom (>= 0.3)
23
23
  tzinfo (~> 2.0, >= 2.0.5)
24
+ addressable (2.8.7)
25
+ public_suffix (>= 2.0.2, < 7.0)
24
26
  ast (2.4.3)
27
+ axiom-types (0.1.1)
28
+ descendants_tracker (~> 0.0.4)
29
+ ice_nine (~> 0.11.0)
30
+ thread_safe (~> 0.3, >= 0.3.1)
25
31
  base64 (0.3.0)
26
32
  benchmark (0.4.1)
27
33
  bigdecimal (3.2.2)
28
34
  bundler-audit (0.9.2)
29
35
  bundler (>= 1.2.0, < 3)
30
36
  thor (~> 1.0)
37
+ childprocess (5.1.0)
38
+ logger (~> 1.5)
39
+ coercible (1.0.0)
40
+ descendants_tracker (~> 0.0.1)
31
41
  concurrent-ruby (1.3.5)
32
42
  connection_pool (2.5.3)
43
+ descendants_tracker (0.0.4)
44
+ thread_safe (~> 0.3, >= 0.3.1)
33
45
  diff-lcs (1.6.2)
34
46
  docile (1.4.1)
35
47
  drb (2.2.3)
48
+ dry-configurable (1.3.0)
49
+ dry-core (~> 1.1)
50
+ zeitwerk (~> 2.6)
51
+ dry-core (1.1.0)
52
+ concurrent-ruby (~> 1.0)
53
+ logger
54
+ zeitwerk (~> 2.6)
55
+ dry-inflector (1.2.0)
56
+ dry-initializer (3.2.0)
57
+ dry-logic (1.6.0)
58
+ bigdecimal
59
+ concurrent-ruby (~> 1.0)
60
+ dry-core (~> 1.1)
61
+ zeitwerk (~> 2.6)
62
+ dry-schema (1.14.1)
63
+ concurrent-ruby (~> 1.0)
64
+ dry-configurable (~> 1.0, >= 1.0.1)
65
+ dry-core (~> 1.1)
66
+ dry-initializer (~> 3.2)
67
+ dry-logic (~> 1.5)
68
+ dry-types (~> 1.8)
69
+ zeitwerk (~> 2.6)
70
+ dry-types (1.8.3)
71
+ bigdecimal (~> 3.0)
72
+ concurrent-ruby (~> 1.0)
73
+ dry-core (~> 1.0)
74
+ dry-inflector (~> 1.0)
75
+ dry-logic (~> 1.4)
76
+ zeitwerk (~> 2.6)
77
+ erubi (1.13.1)
78
+ flay (2.13.3)
79
+ erubi (~> 1.10)
80
+ path_expander (~> 1.0)
81
+ ruby_parser (~> 3.0)
82
+ sexp_processor (~> 4.0)
83
+ flog (4.8.0)
84
+ path_expander (~> 1.0)
85
+ ruby_parser (~> 3.1, > 3.1.0)
86
+ sexp_processor (~> 4.8)
36
87
  humanize (2.5.1)
37
88
  i18n (1.14.7)
38
89
  concurrent-ruby (~> 1.0)
90
+ ice_nine (0.11.2)
39
91
  json (2.13.1)
40
92
  kramdown (2.5.1)
41
93
  rexml (>= 3.3.9)
42
94
  language_server-protocol (3.17.0.5)
95
+ launchy (3.1.1)
96
+ addressable (~> 2.8)
97
+ childprocess (~> 5.0)
98
+ logger (~> 1.6)
43
99
  lint_roller (1.1.0)
44
100
  logger (1.7.0)
45
101
  minitest (5.25.5)
@@ -47,12 +103,20 @@ GEM
47
103
  parser (3.3.9.0)
48
104
  ast (~> 2.4.1)
49
105
  racc
106
+ path_expander (1.1.3)
50
107
  prism (1.4.0)
108
+ public_suffix (6.0.2)
51
109
  racc (1.8.1)
52
110
  rainbow (3.1.1)
53
111
  rake (13.3.0)
112
+ reek (6.5.0)
113
+ dry-schema (~> 1.13)
114
+ logger (~> 1.6)
115
+ parser (~> 3.3.0)
116
+ rainbow (>= 2.0, < 4.0)
117
+ rexml (~> 3.1)
54
118
  regexp_parser (2.10.0)
55
- rexml (3.4.1)
119
+ rexml (3.4.4)
56
120
  rspec (3.13.1)
57
121
  rspec-core (~> 3.13.0)
58
122
  rspec-expectations (~> 3.13.0)
@@ -94,7 +158,23 @@ GEM
94
158
  lint_roller (~> 1.1)
95
159
  rubocop (~> 1.72, >= 1.72.1)
96
160
  ruby-progressbar (1.13.0)
161
+ ruby_parser (3.21.1)
162
+ racc (~> 1.5)
163
+ sexp_processor (~> 4.16)
164
+ rubycritic (4.11.0)
165
+ flay (~> 2.13)
166
+ flog (~> 4.7)
167
+ launchy (>= 2.5.2)
168
+ parser (>= 3.3.0.5)
169
+ rainbow (~> 3.1.1)
170
+ reek (~> 6.5.0, < 7.0)
171
+ rexml
172
+ ruby_parser (~> 3.21)
173
+ simplecov (>= 0.22.0)
174
+ tty-which (~> 0.5.0)
175
+ virtus (~> 2.0)
97
176
  securerandom (0.4.1)
177
+ sexp_processor (4.17.4)
98
178
  simplecov (0.22.0)
99
179
  docile (~> 1.1)
100
180
  simplecov-html (~> 0.11)
@@ -114,12 +194,19 @@ GEM
114
194
  lint_roller (~> 1.1)
115
195
  rubocop-performance (~> 1.25.0)
116
196
  thor (1.4.0)
197
+ thread_safe (0.3.6)
198
+ tty-which (0.5.0)
117
199
  tzinfo (2.0.6)
118
200
  concurrent-ruby (~> 1.0)
119
201
  unicode-display_width (3.1.4)
120
202
  unicode-emoji (~> 4.0, >= 4.0.4)
121
203
  unicode-emoji (4.0.4)
204
+ virtus (2.0.0)
205
+ axiom-types (~> 0.1)
206
+ coercible (~> 1.0)
207
+ descendants_tracker (~> 0.0, >= 0.0.3)
122
208
  yard (0.9.37)
209
+ zeitwerk (2.7.3)
123
210
 
124
211
  PLATFORMS
125
212
  arm64-darwin-22
@@ -136,6 +223,7 @@ DEPENDENCIES
136
223
  rubocop
137
224
  rubocop-rake
138
225
  rubocop-rspec
226
+ rubycritic
139
227
  simplecov
140
228
  standard
141
229
  yard
data/MUSIC_THEORY.md ADDED
@@ -0,0 +1,120 @@
1
+ # Music Theory
2
+ Domain-specific knowledge for this project.
3
+
4
+ ## Rudiments
5
+
6
+ The rudiments of music theory are built up piece-by-piece.
7
+
8
+ letter name
9
+ - C, D, E, F, G, A, B, …
10
+ - equivalent to:
11
+ - do, re, mi, fa, sol, la, si/ti, …
12
+
13
+ alteration
14
+ - sharp, flat, double sharp, double flat
15
+ - optional
16
+ - none is same as 'natural'
17
+ - found in key signatures
18
+ - found in bars as accidentals
19
+
20
+ spelling
21
+ - letter name + alteration
22
+ - example: "E♭"
23
+
24
+ register
25
+ - an integer typically between 0 and 8
26
+ - represents the octaves of an 88-key piano
27
+ - scientific pitch notation
28
+ - middle C is C4
29
+ - increments between B and C
30
+ - a half step below C4 is B3.
31
+
32
+ pitch
33
+ - spelling + register
34
+ - example: "E♭3"
35
+
36
+ rhythmic unit
37
+ - duration
38
+ - expressed in fractions (or multiples) of a standard whole note
39
+ - whole, half, quarter, eighth, sixteenth, …
40
+ - breve, …
41
+
42
+ rhythmic value (duration)
43
+ - a rhythmic unit plus optional augmentation dots
44
+ - augmentation dots extend the duration of the rhythmic unit
45
+ - one dot extends the rhythmic value 50%
46
+ - two dots extend the rhythmic value 75%
47
+ - two dots extend the rhythmic value 87.5%
48
+
49
+ rest
50
+ - a rhythmic value that passes in silence
51
+
52
+ note
53
+ - pitch + rhythmic value
54
+ - example: "E♭3 dotted quarter"
55
+
56
+ unpitched note
57
+ - an unpitched percussion note, such as a drum hit
58
+ - a sounded rhythmic value without a specific pitch
59
+
60
+ tied duration
61
+ - combines multiple rhythmic values into one longer note
62
+ - ties allow rhythmic values to be combined across barlines or strong beats
63
+
64
+ articulation
65
+ - a category of expressions that modify how one or more notes are performed.
66
+ - level of connection
67
+ - staccatissimo - the most staccato, even shorted and more clipped
68
+ - staccato – detatched (shortened to lease space between adjacent notes)
69
+ - tenuto - held of the full value
70
+ - legato - smoothly connected
71
+ - emphasis
72
+ - accent - play with emphasis or a stronger attack
73
+ - marcato - markedly emphasized
74
+ - instrument specific
75
+ - bowings
76
+ - breath mark
77
+ - roll mark
78
+
79
+ ## Instrument Families
80
+
81
+ There are several ways that people talk about families and other categorizations of instruments. For example, the "string family" or "woodwind family", but also more specifically, the word family is applied more specifically (e.g. the "oboe family").
82
+
83
+ ### Text Conversation with Brian Head (May 7, 2023)
84
+
85
+ Brian Head, brother and faculty at USC Thornton School of Music
86
+ https://music.usc.edu/brian-head/
87
+
88
+ Robert Head:
89
+ Hey, Brian. You have a sec for a music question? I’m trying to come up with different terms for the way “family” is used. “Woodwind family” vs. “Oboe family”.
90
+
91
+ Brian Head:
92
+ Those are good, I’d say. In what context?
93
+
94
+ Robert Head:
95
+ In my software project, I’m trying to define those relationships. One is a section of the orchestra and the other is species of instrument. But usually people, in my experience, just say “family”.
96
+ Is there some more precise terminology or adjective that can disambiguate those two terms?
97
+
98
+ Brian Head:
99
+ Hmmm. Already “family” is informal. Blatter distinguishes between “choir” and “family” as in the trumpet family” within the “brass choir”.
100
+
101
+ Robert Head:
102
+ Ah, yes! I do like that.
103
+ But does it apply to percussion?
104
+ “Section” is another candidate, but that gets used for string parts as well.
105
+
106
+ Brian Head:
107
+ Strings and percussion probably don’t think of themselves as a choir, but if you’re mainly looking for a taxonomically consistent word, that’s the feat I can think of at the moment.
108
+ Section is a good word, too, which easily flows between smaller and larger meanings.
109
+
110
+ Robert Head:
111
+ Obviously, the four “families” is a garbage way to classify all instruments and it really is more applicable to the orchestral context, so maybe “orchestra family” or “orchestra section”.
112
+
113
+ Brian Head:
114
+ I’d say that “family” is best used at the instrument level, as in “saxophone family”. Choir or section are better for larger collections. Still, both of those words connote membership in an orchestra. Or large ensemble. “Woodwinds” or “percussion” describe the class of instruments themselves.
115
+
116
+ Robert Head:
117
+ Cool
118
+
119
+ Action Item: Call them orchestra_section
120
+ DONE
data/README.md CHANGED
@@ -101,6 +101,24 @@ bundle exec rake doc
101
101
  - `rake doc_stats` - Show documentation coverage statistics
102
102
  - `rake coverage` - Open coverage report in browser
103
103
 
104
+ ### Releasing a New Version
105
+
106
+ 1. Update the version number in `lib/head_music/version.rb`
107
+ 2. Commit the version change: `git commit -am "Bump version to X.Y.Z"`
108
+ 3. Push to main: `git push origin main`
109
+ 4. Release the gem:
110
+
111
+ ```bash
112
+ bundle exec rake release
113
+ ```
114
+
115
+ This will:
116
+ - Build the gem
117
+ - Create and push a git tag (e.g., `vX.Y.Z`)
118
+ - Push the gem to RubyGems
119
+
120
+ The git tag push also triggers a GitHub Actions workflow that creates a GitHub Release with auto-generated release notes.
121
+
104
122
  ## Contributing
105
123
 
106
124
  We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
data/Rakefile CHANGED
@@ -26,10 +26,15 @@ rescue LoadError
26
26
  # bundler-audit not available
27
27
  end
28
28
 
29
+ desc "Run RubyCritic code quality analysis"
30
+ task :rubycritic do
31
+ sh "rubycritic lib"
32
+ end
33
+
29
34
  task default: :spec
30
35
 
31
- desc "Run all quality checks (tests, linting, security audit)"
32
- task quality: [:spec, :standard, "bundle:audit:check"]
36
+ desc "Run all quality checks (tests, linting, security audit, code quality)"
37
+ task quality: [:spec, :standard, "bundle:audit:check", :rubycritic]
33
38
 
34
39
  desc "Open an irb session preloaded with this library"
35
40
  task :console do
data/head_music.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.required_ruby_version = ">= 3.3.0"
34
34
 
35
- spec.add_runtime_dependency "activesupport", "~> 7.0"
35
+ spec.add_runtime_dependency "activesupport", ">= 7.0", "< 10"
36
36
  spec.add_runtime_dependency "humanize", "~> 2.0"
37
37
  spec.add_runtime_dependency "i18n", "~> 1.8"
38
38