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
data/TODO.md DELETED
@@ -1,109 +0,0 @@
1
- # TODO
2
-
3
- ## User Stories
4
-
5
- Sets
6
- DurationSet?
7
-
8
- Triad
9
- SeventhChord
10
- Don't need anything beyond seventh chords to analyze pre-Romantic music.
11
-
12
-
13
- ## User stories
14
-
15
-
16
- ### Done
17
-
18
- As a developer
19
- When instantiating a DiatonicInterval
20
- When passing an abbreviation, such as 'P5' or 'm2'
21
- I want to receive that instance.
22
-
23
- As a developer
24
- Given a pitch
25
- I want to be able to add a diatonic interval to get another pitch.
26
-
27
- DiatonicInterval
28
- - def above(pitch) -> pitch
29
- DiatonicInterval
30
- - def below(pitch) -> pitch
31
-
32
- Pitch addition and subtraction
33
- - define `Pitch#+`, `Pitch#-`
34
- - use DiatonicInterval methods
35
-
36
- PitchSet
37
-
38
- A PitchSet is unlike a PitchClassSet in that the pitches have spellings with octaves rather than Spellings only or octave-less 0-11 designations.
39
-
40
- PitchClassSet
41
- .size?
42
- .monad?
43
- .dyad?
44
- .triad? (must be stacked thirds to be a 'triad')
45
- .trichord? (all 3-pitch sets)
46
-
47
- Should every group of pitches have one or more strategies for describing it? Such as Dyad?
48
-
49
- Set (superclass?)
50
- PitchSet
51
- EmptySet
52
- Monad
53
- Dyad
54
- Trichord (or Triad)
55
- - triad?
56
- Tetrachord (or Tetrad)
57
- - seventh_chord?
58
- Pentachord (or Pentad)
59
- Hexachord (or Hexad)
60
- Heptachords (or Heptad or, sometimes, mixing Latin and Greek roots, "Septachord")
61
- Octachords (Octad)
62
- Nonachords (Nonad)
63
- Decachords (Decad)
64
- Undecachords
65
- Dodecachord
66
-
67
- PitchClassSet
68
- .normal_form? (most compact)
69
- .prime_form (most compact normal form of the original or any inversion)
70
-
71
-
72
- ## Text Conversation with Brian Head (May 7, 2023)
73
-
74
- Brian Head, brother and faculty at USC Thornton School of Music
75
- https://music.usc.edu/brian-head/
76
-
77
- Robert Head:
78
- 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”.
79
-
80
- Brian Head:
81
- Those are good, I’d say. In what context?
82
-
83
- Robert Head:
84
- 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”.
85
- Is there some more precise terminology or adjective that can disambiguate those two terms?
86
-
87
- Brian Head:
88
- Hmmm. Already “family” is informal. Blatter distinguishes between “choir” and “family” as in the trumpet family” within the “brass choir”.
89
-
90
- Robert Head:
91
- Ah, yes! I do like that.
92
- But does it apply to percussion?
93
- “Section” is another candidate, but that gets used for string parts as well.
94
-
95
- Brian Head:
96
- 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.
97
- Section is a good word, too, which easily flows between smaller and larger meanings.
98
-
99
- Robert Head:
100
- 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”.
101
-
102
- Brian Head:
103
- 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.
104
-
105
- Robert Head:
106
- Cool
107
-
108
- Action Item: Call them orchestra_section
109
- DONE
File without changes
data/test_translations.rb DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # Test the translation loading functionality
4
-
5
- # First test - German translation of solfege
6
- result = system('bundle exec ruby -e "require \'head_music\'; puts HeadMusic::Rudiment::Solmization.get(\'Solfège\')&.name || \'nil\'"')
7
- puts "German 'Solfège' test: #{result}"
8
-
9
- # Second test - Italian translation
10
- result = system('bundle exec ruby -e "require \'head_music\'; puts HeadMusic::Rudiment::Solmization.get(\'solfeggio\')&.name || \'nil\'"')
11
- puts "Italian 'solfeggio' test: #{result}"
12
-
13
- # Third test - Russian translation
14
- result = system('bundle exec ruby -e "require \'head_music\'; puts HeadMusic::Rudiment::Solmization.get(\'сольфеджио\')&.name || \'nil\'"')
15
- puts "Russian 'сольфеджио' test: #{result}"
@@ -1,57 +0,0 @@
1
- # Consonance and Dissonance Classification
2
-
3
- As a music theorist or counterpoint student
4
-
5
- I want to classify intervals by their consonance and dissonance levels
6
-
7
- So that I can apply proper voice leading rules
8
-
9
- ## Scenario: Classify open consonances
10
-
11
- Given I have a perfect fifth or perfect octave
12
-
13
- When I check the consonance classification
14
-
15
- Then it should be identified as "open consonance"
16
-
17
- ## Scenario: Classify soft consonances
18
-
19
- Given I have a third or sixth interval (major or minor)
20
-
21
- When I check the consonance classification
22
-
23
- Then it should be identified as "soft consonance"
24
-
25
- ## Scenario: Classify mild dissonances
26
-
27
- Given I have a major second or minor seventh
28
-
29
- When I check the consonance classification
30
-
31
- Then it should be identified as "mild dissonance"
32
-
33
- ## Scenario: Classify sharp dissonances
34
-
35
- Given I have a minor second or major seventh
36
-
37
- When I check the consonance classification
38
-
39
- Then it should be identified as "sharp dissonance"
40
-
41
- ## Scenario: Handle perfect fourth context
42
-
43
- Given I have a perfect fourth interval
44
-
45
- When I check the consonance classification
46
-
47
- Then it should indicate context-dependent classification
48
-
49
- And note it can be either consonant or dissonant
50
-
51
- ## Scenario: Classify tritone
52
-
53
- Given I have a tritone interval
54
-
55
- When I check the consonance classification
56
-
57
- Then it should be identified as "neutral" or "restless"
@@ -1,62 +0,0 @@
1
- # Pitch Set Classification
2
-
3
- As a music theorist
4
-
5
- I want to classify pitch sets by their size and properties
6
-
7
- So that I can analyze and categorize harmonic structures
8
-
9
- ## Scenario: Identify empty set
10
-
11
- Given I have no pitches
12
-
13
- When I create a pitch set
14
-
15
- Then it should be identified as an EmptySet
16
-
17
- ## Scenario: Identify monad
18
-
19
- Given I have a single pitch
20
-
21
- When I check the pitch set type
22
-
23
- Then it should be identified as a Monad
24
-
25
- And monad? should return true
26
-
27
- ## Scenario: Identify dyad
28
-
29
- Given I have exactly two pitches
30
-
31
- When I check the pitch set type
32
-
33
- Then it should be identified as a Dyad
34
-
35
- And dyad? should return true
36
-
37
- ## Scenario: Distinguish triads from trichords
38
-
39
- Given I have three pitches
40
-
41
- When I analyze the pitch set
42
-
43
- Then trichord? should return true for any 3-pitch set
44
-
45
- And triad? should return true only if they form stacked thirds
46
-
47
- ## Scenario: Identify larger pitch sets
48
-
49
- Given I have a pitch set with N pitches
50
-
51
- When I check the classification
52
-
53
- Then it should be identified as:
54
- - Tetrachord (4 pitches) with seventh_chord? check
55
- - Pentachord (5 pitches)
56
- - Hexachord (6 pitches)
57
- - Heptachord (7 pitches)
58
- - Octachord (8 pitches)
59
- - Nonachord (9 pitches)
60
- - Decachord (10 pitches)
61
- - Undecachord (11 pitches)
62
- - Dodecachord (12 pitches)
@@ -1,47 +0,0 @@
1
- # Sonority Identification
2
-
3
- As a music theorist or composer
4
-
5
- I want to identify and work with named sonorities
6
-
7
- So that I can analyze and create harmonic structures
8
-
9
- ## Scenario: Get sonority by identifier
10
-
11
- Given I need a specific sonority
12
-
13
- When I call Sonority.get with an identifier like "major triad"
14
-
15
- Then I should receive the corresponding sonority object
16
-
17
- And it should contain the correct interval structure
18
-
19
- ## Scenario: Identify sonority from pitch set
20
-
21
- Given I have a set of pitches
22
-
23
- When I call Sonority.for with the pitch set
24
-
25
- Then I should receive the identified sonority
26
-
27
- And it should correctly name the harmonic structure
28
-
29
- ## Scenario: Generate pitch set from sonority
30
-
31
- Given I have a sonority and a root pitch
32
-
33
- When I call Sonority.pitch_set_for with root pitch and inversion
34
-
35
- Then I should receive the correct pitches
36
-
37
- And they should be in the specified inversion
38
-
39
- ## Scenario: Access sonority from pitch set
40
-
41
- Given I have a PitchSet object
42
-
43
- When I call the sonority method
44
-
45
- Then I should receive the corresponding Sonority object
46
-
47
- And it should correctly identify the harmonic content