head_music 9.0.1 → 11.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -3
- data/CHANGELOG.md +18 -0
- data/CLAUDE.md +35 -15
- data/Gemfile +7 -1
- data/Gemfile.lock +91 -3
- data/README.md +18 -0
- data/Rakefile +7 -2
- data/head_music.gemspec +1 -1
- data/lib/head_music/analysis/dyad.rb +229 -0
- data/lib/head_music/analysis/melodic_interval.rb +1 -1
- data/lib/head_music/analysis/pitch_class_set.rb +111 -14
- data/lib/head_music/analysis/{pitch_set.rb → pitch_collection.rb} +11 -5
- data/lib/head_music/analysis/sonority.rb +50 -12
- data/lib/head_music/content/cantus_firmus_examples.rb +58 -0
- data/lib/head_music/content/staff.rb +1 -1
- data/lib/head_music/content/voice.rb +1 -1
- data/lib/head_music/instruments/alternate_tuning.rb +102 -0
- data/lib/head_music/instruments/alternate_tunings.yml +78 -0
- data/lib/head_music/instruments/instrument.rb +251 -82
- data/lib/head_music/instruments/instrument_configuration.rb +66 -0
- data/lib/head_music/instruments/instrument_configuration_option.rb +38 -0
- data/lib/head_music/instruments/instrument_configurations.yml +288 -0
- data/lib/head_music/instruments/instrument_families.yml +77 -0
- data/lib/head_music/instruments/instrument_family.rb +3 -4
- data/lib/head_music/instruments/instruments.yml +795 -965
- data/lib/head_music/instruments/playing_technique.rb +75 -0
- data/lib/head_music/instruments/playing_techniques.yml +826 -0
- data/lib/head_music/instruments/score_order.rb +2 -5
- data/lib/head_music/instruments/staff.rb +61 -1
- data/lib/head_music/instruments/staff_scheme.rb +6 -4
- data/lib/head_music/instruments/stringing.rb +115 -0
- data/lib/head_music/instruments/stringing_course.rb +58 -0
- data/lib/head_music/instruments/stringings.yml +168 -0
- data/lib/head_music/instruments/variant.rb +0 -1
- data/lib/head_music/locales/de.yml +23 -0
- data/lib/head_music/locales/en.yml +100 -0
- data/lib/head_music/locales/es.yml +23 -0
- data/lib/head_music/locales/fr.yml +23 -0
- data/lib/head_music/locales/it.yml +23 -0
- data/lib/head_music/locales/ru.yml +23 -0
- data/lib/head_music/{rudiment → notation}/musical_symbol.rb +3 -3
- data/lib/head_music/notation/staff_mapping.rb +70 -0
- data/lib/head_music/notation/staff_position.rb +62 -0
- data/lib/head_music/notation.rb +7 -0
- data/lib/head_music/rudiment/alteration.rb +17 -47
- data/lib/head_music/rudiment/alterations.yml +32 -0
- data/lib/head_music/rudiment/chromatic_interval.rb +1 -1
- data/lib/head_music/rudiment/clef.rb +1 -1
- data/lib/head_music/rudiment/consonance.rb +14 -13
- data/lib/head_music/rudiment/key_signature.rb +0 -26
- data/lib/head_music/rudiment/rhythmic_unit/parser.rb +2 -2
- data/lib/head_music/rudiment/rhythmic_value/parser.rb +1 -1
- data/lib/head_music/rudiment/rhythmic_value.rb +1 -1
- data/lib/head_music/rudiment/spelling.rb +3 -0
- data/lib/head_music/rudiment/tempo.rb +1 -1
- data/lib/head_music/rudiment/tuning/just_intonation.rb +0 -39
- data/lib/head_music/rudiment/tuning/meantone.rb +0 -39
- data/lib/head_music/rudiment/tuning/pythagorean.rb +0 -39
- data/lib/head_music/rudiment/tuning.rb +20 -0
- data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
- data/lib/head_music/style/modern_tradition.rb +8 -11
- data/lib/head_music/style/tradition.rb +1 -1
- data/lib/head_music/time/clock_position.rb +84 -0
- data/lib/head_music/time/conductor.rb +264 -0
- data/lib/head_music/time/meter_event.rb +37 -0
- data/lib/head_music/time/meter_map.rb +173 -0
- data/lib/head_music/time/musical_position.rb +188 -0
- data/lib/head_music/time/smpte_timecode.rb +164 -0
- data/lib/head_music/time/tempo_event.rb +40 -0
- data/lib/head_music/time/tempo_map.rb +187 -0
- data/lib/head_music/time.rb +32 -0
- data/lib/head_music/utilities/case.rb +27 -0
- data/lib/head_music/utilities/hash_key.rb +1 -1
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +42 -13
- data/user_stories/backlog/notation-style.md +183 -0
- data/user_stories/{todo → backlog}/organizing-content.md +9 -1
- data/user_stories/done/consonance-dissonance-classification.md +117 -0
- data/user_stories/{todo → done}/dyad-analysis.md +4 -6
- data/user_stories/done/expand-playing-techniques.md +38 -0
- data/user_stories/{active → done}/handle-time.rb +5 -19
- data/user_stories/done/instrument-architecture.md +238 -0
- data/user_stories/done/move-musical-symbol-to-notation.md +161 -0
- data/user_stories/done/move-staff-mapping-to-notation.md +158 -0
- data/user_stories/done/move-staff-position-to-notation.md +141 -0
- data/user_stories/done/notation-module-foundation.md +102 -0
- data/user_stories/done/percussion_set.md +260 -0
- data/user_stories/{todo → done}/pitch-class-set-analysis.md +0 -40
- data/user_stories/done/sonority-identification.md +37 -0
- data/user_stories/done/string-pitches.md +41 -0
- data/user_stories/epics/notation-module.md +135 -0
- data/user_stories/{todo → visioning}/agentic-daw.md +0 -1
- metadata +56 -20
- data/check_instrument_consistency.rb +0 -0
- data/lib/head_music/instruments/instrument_type.rb +0 -188
- data/test_translations.rb +0 -15
- data/user_stories/todo/consonance-dissonance-classification.md +0 -57
- data/user_stories/todo/material-and-scores.md +0 -10
- data/user_stories/todo/percussion_set.md +0 -1
- data/user_stories/todo/pitch-set-classification.md +0 -72
- data/user_stories/todo/sonority-identification.md +0 -67
- /data/user_stories/{active → done}/handle-time.md +0 -0
|
@@ -6,46 +6,6 @@ I want to analyze pitch class sets
|
|
|
6
6
|
|
|
7
7
|
So that I can identify set relationships and transformations
|
|
8
8
|
|
|
9
|
-
## Scenario: Get size of pitch class set
|
|
10
|
-
|
|
11
|
-
Given I have a pitch class set
|
|
12
|
-
|
|
13
|
-
When I call the size method
|
|
14
|
-
|
|
15
|
-
Then it should return the number of unique pitch classes
|
|
16
|
-
|
|
17
|
-
## Scenario: Check if monad
|
|
18
|
-
|
|
19
|
-
Given I have a pitch class set with one pitch class
|
|
20
|
-
|
|
21
|
-
When I call monad?
|
|
22
|
-
|
|
23
|
-
Then it should return true
|
|
24
|
-
|
|
25
|
-
## Scenario: Check if dyad
|
|
26
|
-
|
|
27
|
-
Given I have a pitch class set with two pitch classes
|
|
28
|
-
|
|
29
|
-
When I call dyad?
|
|
30
|
-
|
|
31
|
-
Then it should return true
|
|
32
|
-
|
|
33
|
-
## Scenario: Check if triad
|
|
34
|
-
|
|
35
|
-
Given I have a pitch class set with three pitch classes
|
|
36
|
-
|
|
37
|
-
When I call triad?
|
|
38
|
-
|
|
39
|
-
Then it should return true only if they form stacked thirds
|
|
40
|
-
|
|
41
|
-
## Scenario: Check if trichord
|
|
42
|
-
|
|
43
|
-
Given I have a pitch class set with three pitch classes
|
|
44
|
-
|
|
45
|
-
When I call trichord?
|
|
46
|
-
|
|
47
|
-
Then it should return true for any 3-pitch class set
|
|
48
|
-
|
|
49
9
|
## Scenario: Find normal form
|
|
50
10
|
|
|
51
11
|
Given I have a pitch class set
|
|
@@ -0,0 +1,37 @@
|
|
|
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: Generate pitch collection from sonority
|
|
20
|
+
|
|
21
|
+
Given I have a sonority identifier and a root pitch
|
|
22
|
+
|
|
23
|
+
When I call a method to generate pitches
|
|
24
|
+
|
|
25
|
+
Then I should receive the correct pitches for that sonority
|
|
26
|
+
|
|
27
|
+
And they should be in the specified inversion
|
|
28
|
+
|
|
29
|
+
## Scenario: Access sonority from pitch collection
|
|
30
|
+
|
|
31
|
+
Given I have a PitchCollection object
|
|
32
|
+
|
|
33
|
+
When I call the sonority method
|
|
34
|
+
|
|
35
|
+
Then I should receive the corresponding Sonority object
|
|
36
|
+
|
|
37
|
+
And it should correctly identify the harmonic content
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Instruments can have strings with pitches
|
|
2
|
+
|
|
3
|
+
Instruments like violins and guitars have strings that are each tuned to a specific pitch.
|
|
4
|
+
|
|
5
|
+
## Implementation Plan
|
|
6
|
+
|
|
7
|
+
HeadMusic::Instruments::Instrument
|
|
8
|
+
|
|
9
|
+
HeadMusic::Instruments::Stringing
|
|
10
|
+
> instrument: Instrument
|
|
11
|
+
>> stringing_courses: StringingCourse[]
|
|
12
|
+
|
|
13
|
+
HeadMusic::Instruments::StringingCourse
|
|
14
|
+
> stringing: Stringing
|
|
15
|
+
> standard_pitch: HeadMusic::Rudiment::Pitch
|
|
16
|
+
for example, the lowest string of a guitar, "E2"
|
|
17
|
+
- course_semitones: Integer[]
|
|
18
|
+
Examples:
|
|
19
|
+
a 6-string guitar would be []
|
|
20
|
+
a 12-string guitar would be [12] for the low strings and [0] for the high strings
|
|
21
|
+
|
|
22
|
+
HeadMusic::Instruments::AlternateTuning
|
|
23
|
+
> instrument: Instrument
|
|
24
|
+
- name: string
|
|
25
|
+
>> semitones: int[]
|
|
26
|
+
lowest to highest string courses in the strings
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Answering questions
|
|
30
|
+
|
|
31
|
+
> Would it make more sense for Stringing to be associated with InstrumentConfiguration (the variant) rather than Instrument directly?
|
|
32
|
+
|
|
33
|
+
No, each instrument could have a Stringing object. If not, it would inherit from it's ancestry, just like most Instrument attributes.
|
|
34
|
+
|
|
35
|
+
> Is the semitone offset approach intentional for simplicity, or would you prefer something more explicit?
|
|
36
|
+
|
|
37
|
+
It is intentional. That way, the courses are independent of the specific tunings.
|
|
38
|
+
|
|
39
|
+
> Overlap Between standard_pitch and StringingTuning
|
|
40
|
+
|
|
41
|
+
The standard pitch is the unconfigured pitch of the string intrinsic to standard practice for that instrument. The Tuning objects configure alternative sets of pitches
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# EPIC: Notation Module
|
|
2
|
+
|
|
3
|
+
AS a developer
|
|
4
|
+
|
|
5
|
+
I WANT to organize notation-related features in a dedicated HeadMusic::Notation module
|
|
6
|
+
|
|
7
|
+
SO THAT I can clearly separate visual representation concerns from music theory, instrument properties, and musical content
|
|
8
|
+
|
|
9
|
+
## Vision
|
|
10
|
+
|
|
11
|
+
Music notation is about visual representation - how musical concepts appear on paper, screen, or other media. This is conceptually distinct from:
|
|
12
|
+
- **Music theory** (abstract concepts like pitch, interval, harmony)
|
|
13
|
+
- **Instrument properties** (range, transposition, acoustic characteristics)
|
|
14
|
+
- **Musical content** (compositions, temporal organization)
|
|
15
|
+
|
|
16
|
+
A dedicated Notation module provides a clear home for all visual representation concerns, making the codebase more organized and enabling future expansion into comprehensive music engraving capabilities.
|
|
17
|
+
|
|
18
|
+
## Background
|
|
19
|
+
|
|
20
|
+
Currently, notation-related code is scattered across multiple modules:
|
|
21
|
+
- **HeadMusic::Instruments** contains `StaffPosition`, `Staff`, `StaffMapping`, and `StaffScheme`
|
|
22
|
+
- **HeadMusic::Rudiment** contains `Clef`, `MusicalSymbol`, and notation aspects of `Alteration` and `RhythmicUnit`
|
|
23
|
+
- **HeadMusic::Content** has its own minimal `Staff` class
|
|
24
|
+
|
|
25
|
+
A TODO comment in `lib/head_music/instruments/staff_position.rb:5` suggests: "consider moving to a HeadMusic::Notation module"
|
|
26
|
+
|
|
27
|
+
This scattered organization makes it unclear where new notation features should live and conflates distinct concerns.
|
|
28
|
+
|
|
29
|
+
## Module Boundaries
|
|
30
|
+
|
|
31
|
+
### HeadMusic::Notation (visual representation)
|
|
32
|
+
- Staff positions, lines, spaces, ledger lines
|
|
33
|
+
- Musical symbols (ASCII, Unicode, HTML entities)
|
|
34
|
+
- Notehead shapes and placement on staff
|
|
35
|
+
- Stem direction and flags
|
|
36
|
+
- Clef symbols and their staff placement
|
|
37
|
+
- Accidental symbol placement
|
|
38
|
+
- Future: beaming, ties, slurs, articulations, dynamics
|
|
39
|
+
|
|
40
|
+
### HeadMusic::Rudiment (music theory)
|
|
41
|
+
- Abstract concepts: pitch, interval, scale, chord
|
|
42
|
+
- Duration concepts (without visual representation)
|
|
43
|
+
- Keys, meters (as musical concepts, not visual symbols)
|
|
44
|
+
- Core theory that notation represents visually
|
|
45
|
+
|
|
46
|
+
### HeadMusic::Instruments (instrument properties)
|
|
47
|
+
- Instrument families and classification
|
|
48
|
+
- Pitch ranges and transposition
|
|
49
|
+
- Playing techniques
|
|
50
|
+
- Score ordering
|
|
51
|
+
- References to default clef (but doesn't own clef rendering)
|
|
52
|
+
|
|
53
|
+
### HeadMusic::Content (musical content)
|
|
54
|
+
- Compositions, voices, bars
|
|
55
|
+
- Notes in context (pitch + duration + placement)
|
|
56
|
+
- Temporal organization
|
|
57
|
+
- Uses Notation for visual representation
|
|
58
|
+
|
|
59
|
+
## Scope
|
|
60
|
+
|
|
61
|
+
### Phase 1: Foundation & Core Moves (Current Focus)
|
|
62
|
+
- Establish module infrastructure
|
|
63
|
+
- Move `StaffPosition` from Instruments to Notation
|
|
64
|
+
- Move `MusicalSymbol` from Rudiment to Notation
|
|
65
|
+
- Move `StaffMapping` from Instruments to Notation
|
|
66
|
+
|
|
67
|
+
**User Stories:**
|
|
68
|
+
- [Notation Module Foundation](../backlog/notation-module-foundation.md)
|
|
69
|
+
- [Move StaffPosition to Notation](../backlog/move-staff-position-to-notation.md)
|
|
70
|
+
- [Move MusicalSymbol to Notation](../backlog/move-musical-symbol-to-notation.md)
|
|
71
|
+
- [Move StaffMapping to Notation](../backlog/move-staff-mapping-to-notation.md)
|
|
72
|
+
|
|
73
|
+
### Phase 2: Extract Notation Aspects (Future)
|
|
74
|
+
- Create `ClefPlacement` - extract visual clef positioning from `Rudiment::Clef`
|
|
75
|
+
- Create `AccidentalPlacement` - extract accidental display rules from `Rudiment::Alteration`
|
|
76
|
+
- Create `RhythmicNotation` - extract notehead, stem, flag logic from `Rudiment::RhythmicUnit`
|
|
77
|
+
- Refactor `StaffScheme` to `Notation::StaffSystem`
|
|
78
|
+
|
|
79
|
+
### Phase 3: Advanced Notation Features (Long-term Vision)
|
|
80
|
+
- Beaming (connecting note stems across beats)
|
|
81
|
+
- Stems (direction and length calculation)
|
|
82
|
+
- Ties (connecting same pitches across bars)
|
|
83
|
+
- Slurs (phrase markings)
|
|
84
|
+
- Articulations (staccato, accent, tenuto, etc.)
|
|
85
|
+
- Dynamics (forte, piano, crescendo, etc.)
|
|
86
|
+
- Tuplets (triplets, quintuplets, etc.)
|
|
87
|
+
- Barlines (single, double, repeat)
|
|
88
|
+
- Ornaments (trills, mordents, turns)
|
|
89
|
+
- Fermatas (pause markings)
|
|
90
|
+
|
|
91
|
+
### Beyond Western Staff Notation (Future Exploration)
|
|
92
|
+
|
|
93
|
+
The Notation module could eventually support multiple notation systems:
|
|
94
|
+
|
|
95
|
+
**Traditional notation systems:**
|
|
96
|
+
- Western staff notation (current focus)
|
|
97
|
+
- Tablature (tab) - finger positions for guitar, bass, lute
|
|
98
|
+
- Shaped note notation - different note head shapes for scale degrees
|
|
99
|
+
- Drum notation - modified staff notation for percussion
|
|
100
|
+
|
|
101
|
+
**Alphanumeric and shorthand systems:**
|
|
102
|
+
- Lead sheet notation - melody with chord symbols
|
|
103
|
+
- Nashville Number System - scale degrees instead of chord names
|
|
104
|
+
- Roman numeral analysis - functional harmony labels
|
|
105
|
+
- ABC notation - text-based system for folk music
|
|
106
|
+
- Figured bass - Baroque-era interval shorthand
|
|
107
|
+
|
|
108
|
+
**Digital/computer formats:**
|
|
109
|
+
- MIDI - note events with pitch, velocity, timing
|
|
110
|
+
- MusicXML - XML-based interchange format
|
|
111
|
+
- Lilypond - text-based music engraving
|
|
112
|
+
- MEI (Music Encoding Initiative) - scholarly encoding
|
|
113
|
+
|
|
114
|
+
## Success Criteria
|
|
115
|
+
|
|
116
|
+
**Module Organization:**
|
|
117
|
+
- Clear separation between theory (Rudiment), notation (Notation), instruments (Instruments), and content (Content)
|
|
118
|
+
- All visual representation concerns organized under `HeadMusic::Notation`
|
|
119
|
+
- Documentation clearly explains what belongs in each module
|
|
120
|
+
|
|
121
|
+
**Code Quality:**
|
|
122
|
+
- All moved classes maintain existing functionality
|
|
123
|
+
- 90%+ test coverage maintained throughout
|
|
124
|
+
- No breaking changes for internal usage
|
|
125
|
+
- All TODO comments resolved
|
|
126
|
+
|
|
127
|
+
**Extensibility:**
|
|
128
|
+
- Module designed for future expansion (beams, ties, dynamics, etc.)
|
|
129
|
+
- Support for multiple output formats (Unicode, MusicXML, SVG)
|
|
130
|
+
- Clear patterns established for adding new notation features
|
|
131
|
+
|
|
132
|
+
**Developer Experience:**
|
|
133
|
+
- Clear guidance in CLAUDE.md about where notation features belong
|
|
134
|
+
- Easy to find and use notation-related classes
|
|
135
|
+
- Logical organization that matches mental model of music notation
|
metadata
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: head_music
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 11.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rob Head
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '7.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '10'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '7.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '10'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: humanize
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -156,7 +162,6 @@ files:
|
|
|
156
162
|
- bin/check_instrument_consistency.rb
|
|
157
163
|
- bin/console
|
|
158
164
|
- bin/setup
|
|
159
|
-
- check_instrument_consistency.rb
|
|
160
165
|
- head_music.gemspec
|
|
161
166
|
- lib/head_music.rb
|
|
162
167
|
- lib/head_music/analysis/circle.rb
|
|
@@ -166,30 +171,41 @@ files:
|
|
|
166
171
|
- lib/head_music/analysis/diatonic_interval/parser.rb
|
|
167
172
|
- lib/head_music/analysis/diatonic_interval/semitones.rb
|
|
168
173
|
- lib/head_music/analysis/diatonic_interval/size.rb
|
|
174
|
+
- lib/head_music/analysis/dyad.rb
|
|
169
175
|
- lib/head_music/analysis/harmonic_interval.rb
|
|
170
176
|
- lib/head_music/analysis/interval_consonance.rb
|
|
171
177
|
- lib/head_music/analysis/interval_cycle.rb
|
|
172
178
|
- lib/head_music/analysis/melodic_interval.rb
|
|
173
179
|
- lib/head_music/analysis/motion.rb
|
|
174
180
|
- lib/head_music/analysis/pitch_class_set.rb
|
|
175
|
-
- lib/head_music/analysis/
|
|
181
|
+
- lib/head_music/analysis/pitch_collection.rb
|
|
176
182
|
- lib/head_music/analysis/sonority.rb
|
|
177
183
|
- lib/head_music/content/bar.rb
|
|
184
|
+
- lib/head_music/content/cantus_firmus_examples.rb
|
|
178
185
|
- lib/head_music/content/composition.rb
|
|
179
186
|
- lib/head_music/content/note.rb
|
|
180
187
|
- lib/head_music/content/placement.rb
|
|
181
188
|
- lib/head_music/content/position.rb
|
|
182
189
|
- lib/head_music/content/staff.rb
|
|
183
190
|
- lib/head_music/content/voice.rb
|
|
191
|
+
- lib/head_music/instruments/alternate_tuning.rb
|
|
192
|
+
- lib/head_music/instruments/alternate_tunings.yml
|
|
184
193
|
- lib/head_music/instruments/instrument.rb
|
|
194
|
+
- lib/head_music/instruments/instrument_configuration.rb
|
|
195
|
+
- lib/head_music/instruments/instrument_configuration_option.rb
|
|
196
|
+
- lib/head_music/instruments/instrument_configurations.yml
|
|
185
197
|
- lib/head_music/instruments/instrument_families.yml
|
|
186
198
|
- lib/head_music/instruments/instrument_family.rb
|
|
187
|
-
- lib/head_music/instruments/instrument_type.rb
|
|
188
199
|
- lib/head_music/instruments/instruments.yml
|
|
200
|
+
- lib/head_music/instruments/playing_technique.rb
|
|
201
|
+
- lib/head_music/instruments/playing_techniques.yml
|
|
189
202
|
- lib/head_music/instruments/score_order.rb
|
|
190
203
|
- lib/head_music/instruments/score_orders.yml
|
|
191
204
|
- lib/head_music/instruments/staff.rb
|
|
192
205
|
- lib/head_music/instruments/staff_scheme.rb
|
|
206
|
+
- lib/head_music/instruments/stringing.rb
|
|
207
|
+
- lib/head_music/instruments/stringing_course.rb
|
|
208
|
+
- lib/head_music/instruments/stringings.yml
|
|
193
209
|
- lib/head_music/instruments/variant.rb
|
|
194
210
|
- lib/head_music/locales/de.yml
|
|
195
211
|
- lib/head_music/locales/en.yml
|
|
@@ -199,7 +215,12 @@ files:
|
|
|
199
215
|
- lib/head_music/locales/it.yml
|
|
200
216
|
- lib/head_music/locales/ru.yml
|
|
201
217
|
- lib/head_music/named.rb
|
|
218
|
+
- lib/head_music/notation.rb
|
|
219
|
+
- lib/head_music/notation/musical_symbol.rb
|
|
220
|
+
- lib/head_music/notation/staff_mapping.rb
|
|
221
|
+
- lib/head_music/notation/staff_position.rb
|
|
202
222
|
- lib/head_music/rudiment/alteration.rb
|
|
223
|
+
- lib/head_music/rudiment/alterations.yml
|
|
203
224
|
- lib/head_music/rudiment/base.rb
|
|
204
225
|
- lib/head_music/rudiment/chromatic_interval.rb
|
|
205
226
|
- lib/head_music/rudiment/clef.rb
|
|
@@ -212,7 +233,6 @@ files:
|
|
|
212
233
|
- lib/head_music/rudiment/letter_name.rb
|
|
213
234
|
- lib/head_music/rudiment/meter.rb
|
|
214
235
|
- lib/head_music/rudiment/mode.rb
|
|
215
|
-
- lib/head_music/rudiment/musical_symbol.rb
|
|
216
236
|
- lib/head_music/rudiment/note.rb
|
|
217
237
|
- lib/head_music/rudiment/pitch.rb
|
|
218
238
|
- lib/head_music/rudiment/pitch/enharmonic_equivalence.rb
|
|
@@ -287,26 +307,42 @@ files:
|
|
|
287
307
|
- lib/head_music/style/modern_tradition.rb
|
|
288
308
|
- lib/head_music/style/renaissance_tradition.rb
|
|
289
309
|
- lib/head_music/style/tradition.rb
|
|
310
|
+
- lib/head_music/time.rb
|
|
311
|
+
- lib/head_music/time/clock_position.rb
|
|
312
|
+
- lib/head_music/time/conductor.rb
|
|
313
|
+
- lib/head_music/time/meter_event.rb
|
|
314
|
+
- lib/head_music/time/meter_map.rb
|
|
315
|
+
- lib/head_music/time/musical_position.rb
|
|
316
|
+
- lib/head_music/time/smpte_timecode.rb
|
|
317
|
+
- lib/head_music/time/tempo_event.rb
|
|
318
|
+
- lib/head_music/time/tempo_map.rb
|
|
319
|
+
- lib/head_music/utilities/case.rb
|
|
290
320
|
- lib/head_music/utilities/hash_key.rb
|
|
291
321
|
- lib/head_music/version.rb
|
|
292
|
-
-
|
|
293
|
-
- user_stories/
|
|
294
|
-
- user_stories/
|
|
322
|
+
- user_stories/backlog/notation-style.md
|
|
323
|
+
- user_stories/backlog/organizing-content.md
|
|
324
|
+
- user_stories/done/consonance-dissonance-classification.md
|
|
325
|
+
- user_stories/done/dyad-analysis.md
|
|
295
326
|
- user_stories/done/epic--score-order/PLAN.md
|
|
296
327
|
- user_stories/done/epic--score-order/band-score-order.md
|
|
297
328
|
- user_stories/done/epic--score-order/chamber-ensemble-score-order.md
|
|
298
329
|
- user_stories/done/epic--score-order/orchestral-score-order.md
|
|
330
|
+
- user_stories/done/expand-playing-techniques.md
|
|
331
|
+
- user_stories/done/handle-time.md
|
|
332
|
+
- user_stories/done/handle-time.rb
|
|
333
|
+
- user_stories/done/instrument-architecture.md
|
|
299
334
|
- user_stories/done/instrument-variant.md
|
|
335
|
+
- user_stories/done/move-musical-symbol-to-notation.md
|
|
336
|
+
- user_stories/done/move-staff-mapping-to-notation.md
|
|
337
|
+
- user_stories/done/move-staff-position-to-notation.md
|
|
338
|
+
- user_stories/done/notation-module-foundation.md
|
|
339
|
+
- user_stories/done/percussion_set.md
|
|
340
|
+
- user_stories/done/pitch-class-set-analysis.md
|
|
341
|
+
- user_stories/done/sonority-identification.md
|
|
342
|
+
- user_stories/done/string-pitches.md
|
|
300
343
|
- user_stories/done/superclass-for-note.md
|
|
301
|
-
- user_stories/
|
|
302
|
-
- user_stories/
|
|
303
|
-
- user_stories/todo/dyad-analysis.md
|
|
304
|
-
- user_stories/todo/material-and-scores.md
|
|
305
|
-
- user_stories/todo/organizing-content.md
|
|
306
|
-
- user_stories/todo/percussion_set.md
|
|
307
|
-
- user_stories/todo/pitch-class-set-analysis.md
|
|
308
|
-
- user_stories/todo/pitch-set-classification.md
|
|
309
|
-
- user_stories/todo/sonority-identification.md
|
|
344
|
+
- user_stories/epics/notation-module.md
|
|
345
|
+
- user_stories/visioning/agentic-daw.md
|
|
310
346
|
homepage: https://github.com/roberthead/head_music
|
|
311
347
|
licenses:
|
|
312
348
|
- MIT
|
|
File without changes
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# Namespace for instrument definitions, categorization, and configuration
|
|
2
|
-
module HeadMusic::Instruments; end
|
|
3
|
-
|
|
4
|
-
# A musical instrument type representing a catalog entry.
|
|
5
|
-
# An instrument type defines the base characteristics and available variants for an instrument.
|
|
6
|
-
# Attributes:
|
|
7
|
-
# name_key: the name of the instrument type
|
|
8
|
-
# alias_name_keys: an array of alternative names for the instrument type
|
|
9
|
-
# orchestra_section_key: the section of the orchestra (e.g. "strings")
|
|
10
|
-
# family_key: the key for the family of the instrument (e.g. "saxophone")
|
|
11
|
-
# classification_keys: an array of classification_keys
|
|
12
|
-
# default_clefs: the default clef or system of clefs for the instrument type
|
|
13
|
-
# - [treble] for instruments that use the treble clef
|
|
14
|
-
# - [treble, bass] for instruments that use the grand staff
|
|
15
|
-
# variants:
|
|
16
|
-
# a hash of default and alternative pitch designations
|
|
17
|
-
# Associations:
|
|
18
|
-
# family: the family of the instrument (e.g. "saxophone")
|
|
19
|
-
# orchestra_section: the section of the orchestra (e.g. "strings")
|
|
20
|
-
class HeadMusic::Instruments::InstrumentType
|
|
21
|
-
include HeadMusic::Named
|
|
22
|
-
|
|
23
|
-
INSTRUMENTS = YAML.load_file(File.expand_path("instruments.yml", __dir__)).freeze
|
|
24
|
-
|
|
25
|
-
def self.get(name)
|
|
26
|
-
get_by_name(name)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def self.all
|
|
30
|
-
HeadMusic::Instruments::InstrumentFamily.all
|
|
31
|
-
@all ||=
|
|
32
|
-
INSTRUMENTS.map { |key, _data| get(key) }.sort_by { |instrument| instrument.name.downcase }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
attr_reader(
|
|
36
|
-
:name_key, :alias_name_keys,
|
|
37
|
-
:family_key, :orchestra_section_key,
|
|
38
|
-
:variants, :classification_keys
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
def ==(other)
|
|
42
|
-
to_s == other.to_s
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def translation(locale = :en)
|
|
46
|
-
return name unless name_key
|
|
47
|
-
|
|
48
|
-
I18n.translate(name_key, scope: %i[head_music instruments], locale: locale, default: name)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def family
|
|
52
|
-
return unless family_key
|
|
53
|
-
|
|
54
|
-
HeadMusic::Instruments::InstrumentFamily.get(family_key)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Returns true if the instrument sounds at a different pitch than written.
|
|
58
|
-
def transposing?
|
|
59
|
-
default_sounding_transposition != 0
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Returns true if the instrument sounds at a different register than written.
|
|
63
|
-
def transposing_at_the_octave?
|
|
64
|
-
transposing? && default_sounding_transposition % 12 == 0
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def single_staff?
|
|
68
|
-
default_staves.length == 1
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def multiple_staves?
|
|
72
|
-
default_staves.length > 1
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def pitched?
|
|
76
|
-
return false if default_clefs.compact.uniq == [HeadMusic::Rudiment::Clef.get("neutral_clef")]
|
|
77
|
-
|
|
78
|
-
default_clefs.any?
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def default_variant
|
|
82
|
-
variants&.find(&:default?) || variants&.first
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def default_instrument
|
|
86
|
-
@default_instrument ||= HeadMusic::Instruments::Instrument.new(self, default_variant)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def default_staff_scheme
|
|
90
|
-
default_variant&.default_staff_scheme
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def default_staves
|
|
94
|
-
default_staff_scheme&.staves || []
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def default_clefs
|
|
98
|
-
default_staves&.map(&:clef) || []
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def default_sounding_transposition
|
|
102
|
-
default_staves&.first&.sounding_transposition || 0
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
private_class_method :new
|
|
106
|
-
|
|
107
|
-
private
|
|
108
|
-
|
|
109
|
-
def initialize(name)
|
|
110
|
-
record = record_for_name(name)
|
|
111
|
-
if record
|
|
112
|
-
initialize_data_from_record(record)
|
|
113
|
-
else
|
|
114
|
-
self.name = name.to_s
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def record_for_name(name)
|
|
119
|
-
record_for_key(HeadMusic::Utilities::HashKey.for(name)) ||
|
|
120
|
-
record_for_key(key_for_name(name)) ||
|
|
121
|
-
record_for_alias(name)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def key_for_name(name)
|
|
125
|
-
INSTRUMENTS.each do |key, _data|
|
|
126
|
-
I18n.config.available_locales.each do |locale|
|
|
127
|
-
translation = I18n.t("head_music.instruments.#{key}", locale: locale)
|
|
128
|
-
return key if translation.downcase == name.downcase
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
nil
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def record_for_key(key)
|
|
135
|
-
INSTRUMENTS.each do |name_key, data|
|
|
136
|
-
return data.merge!("name_key" => name_key) if name_key.to_s == key.to_s
|
|
137
|
-
end
|
|
138
|
-
nil
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def record_for_alias(name)
|
|
142
|
-
normalized_name = HeadMusic::Utilities::HashKey.for(name).to_s
|
|
143
|
-
INSTRUMENTS.each do |name_key, data|
|
|
144
|
-
data["alias_name_keys"]&.each do |alias_key|
|
|
145
|
-
return data.merge!("name_key" => name_key) if HeadMusic::Utilities::HashKey.for(alias_key).to_s == normalized_name
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
nil
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def initialize_data_from_record(record)
|
|
152
|
-
initialize_family(record)
|
|
153
|
-
inherit_family_attributes(record)
|
|
154
|
-
initialize_names(record)
|
|
155
|
-
initialize_attributes(record)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def initialize_family(record)
|
|
159
|
-
@family_key = record["family_key"]
|
|
160
|
-
@family = HeadMusic::Instruments::InstrumentFamily.get(family_key)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def inherit_family_attributes(record)
|
|
164
|
-
return unless family
|
|
165
|
-
|
|
166
|
-
@orchestra_section_key = family.orchestra_section_key
|
|
167
|
-
@classification_keys = family.classification_keys || []
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def initialize_names(record)
|
|
171
|
-
@name_key = record["name_key"].to_sym
|
|
172
|
-
self.name = I18n.translate(name_key, scope: "head_music.instruments", locale: "en", default: inferred_name)
|
|
173
|
-
@alias_name_keys = record["alias_name_keys"] || []
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def initialize_attributes(record)
|
|
177
|
-
@orchestra_section_key ||= record["orchestra_section_key"]
|
|
178
|
-
@classification_keys = [@classification_keys, record["classification_keys"]].flatten.compact.uniq
|
|
179
|
-
@variants =
|
|
180
|
-
(record["variants"] || {}).map do |key, attributes|
|
|
181
|
-
HeadMusic::Instruments::Variant.new(key, attributes)
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def inferred_name
|
|
186
|
-
name_key.to_s.tr("_", " ")
|
|
187
|
-
end
|
|
188
|
-
end
|
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}"
|