head_music 8.2.1 → 9.0.1
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 +1 -1
- data/.github/workflows/release.yml +1 -1
- data/CHANGELOG.md +53 -0
- data/CLAUDE.md +151 -0
- data/Gemfile.lock +25 -25
- data/MUSIC_THEORY.md +120 -0
- data/Rakefile +2 -2
- data/bin/check_instrument_consistency.rb +86 -0
- data/check_instrument_consistency.rb +0 -0
- data/head_music.gemspec +1 -1
- data/lib/head_music/analysis/diatonic_interval/naming.rb +1 -1
- data/lib/head_music/analysis/diatonic_interval.rb +50 -27
- data/lib/head_music/analysis/interval_consonance.rb +51 -0
- data/lib/head_music/content/note.rb +1 -1
- data/lib/head_music/content/placement.rb +1 -1
- data/lib/head_music/content/position.rb +1 -1
- data/lib/head_music/content/staff.rb +1 -1
- data/lib/head_music/instruments/instrument.rb +103 -113
- data/lib/head_music/instruments/instrument_families.yml +10 -9
- data/lib/head_music/instruments/instrument_family.rb +13 -2
- data/lib/head_music/instruments/instrument_type.rb +188 -0
- data/lib/head_music/instruments/instruments.yml +350 -368
- data/lib/head_music/instruments/score_order.rb +139 -0
- data/lib/head_music/instruments/score_orders.yml +130 -0
- data/lib/head_music/instruments/variant.rb +6 -0
- data/lib/head_music/locales/de.yml +6 -0
- data/lib/head_music/locales/en.yml +98 -0
- data/lib/head_music/locales/es.yml +6 -0
- data/lib/head_music/locales/fr.yml +6 -0
- data/lib/head_music/locales/it.yml +6 -0
- data/lib/head_music/locales/ru.yml +6 -0
- data/lib/head_music/rudiment/alteration.rb +23 -8
- data/lib/head_music/rudiment/base.rb +9 -0
- data/lib/head_music/rudiment/chromatic_interval.rb +3 -6
- data/lib/head_music/rudiment/clef.rb +1 -1
- data/lib/head_music/rudiment/consonance.rb +37 -4
- data/lib/head_music/rudiment/diatonic_context.rb +25 -0
- data/lib/head_music/rudiment/key.rb +77 -0
- data/lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb +1 -1
- data/lib/head_music/rudiment/key_signature.rb +46 -7
- data/lib/head_music/rudiment/letter_name.rb +3 -3
- data/lib/head_music/rudiment/meter.rb +19 -9
- data/lib/head_music/rudiment/mode.rb +92 -0
- data/lib/head_music/rudiment/musical_symbol.rb +1 -1
- data/lib/head_music/rudiment/note.rb +112 -0
- data/lib/head_music/rudiment/pitch/parser.rb +52 -0
- data/lib/head_music/rudiment/pitch.rb +5 -6
- data/lib/head_music/rudiment/pitch_class.rb +1 -1
- data/lib/head_music/rudiment/quality.rb +1 -1
- data/lib/head_music/rudiment/reference_pitch.rb +1 -1
- data/lib/head_music/rudiment/register.rb +4 -1
- data/lib/head_music/rudiment/rest.rb +36 -0
- data/lib/head_music/rudiment/rhythmic_element.rb +53 -0
- data/lib/head_music/rudiment/rhythmic_unit/parser.rb +86 -0
- data/lib/head_music/rudiment/rhythmic_unit.rb +104 -29
- data/lib/head_music/rudiment/rhythmic_units.yml +80 -0
- data/lib/head_music/rudiment/rhythmic_value/parser.rb +77 -0
- data/lib/head_music/{content → rudiment}/rhythmic_value.rb +23 -5
- data/lib/head_music/rudiment/scale.rb +4 -5
- data/lib/head_music/rudiment/scale_degree.rb +9 -4
- data/lib/head_music/rudiment/scale_type.rb +9 -3
- data/lib/head_music/rudiment/solmization.rb +1 -1
- data/lib/head_music/rudiment/spelling.rb +5 -4
- data/lib/head_music/rudiment/tempo.rb +85 -0
- data/lib/head_music/rudiment/tonal_context.rb +35 -0
- data/lib/head_music/rudiment/tuning/just_intonation.rb +85 -0
- data/lib/head_music/rudiment/tuning/meantone.rb +87 -0
- data/lib/head_music/rudiment/tuning/pythagorean.rb +91 -0
- data/lib/head_music/rudiment/tuning.rb +18 -4
- data/lib/head_music/rudiment/unpitched_note.rb +62 -0
- data/lib/head_music/style/annotation.rb +4 -4
- data/lib/head_music/style/guidelines/notes_same_length.rb +16 -16
- data/lib/head_music/style/medieval_tradition.rb +26 -0
- data/lib/head_music/style/modern_tradition.rb +34 -0
- data/lib/head_music/style/renaissance_tradition.rb +26 -0
- data/lib/head_music/style/tradition.rb +21 -0
- data/lib/head_music/utilities/hash_key.rb +34 -2
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +33 -9
- data/user_stories/active/handle-time.md +7 -0
- data/user_stories/active/handle-time.rb +177 -0
- data/user_stories/done/epic--score-order/PLAN.md +244 -0
- data/user_stories/done/epic--score-order/band-score-order.md +38 -0
- data/user_stories/done/epic--score-order/chamber-ensemble-score-order.md +33 -0
- data/user_stories/done/epic--score-order/orchestral-score-order.md +43 -0
- data/user_stories/done/instrument-variant.md +65 -0
- data/user_stories/done/superclass-for-note.md +30 -0
- data/user_stories/todo/agentic-daw.md +3 -0
- data/user_stories/todo/consonance-dissonance-classification.md +57 -0
- data/user_stories/todo/dyad-analysis.md +57 -0
- data/user_stories/todo/material-and-scores.md +10 -0
- data/user_stories/todo/organizing-content.md +72 -0
- data/user_stories/todo/percussion_set.md +1 -0
- data/user_stories/todo/pitch-class-set-analysis.md +79 -0
- data/user_stories/todo/pitch-set-classification.md +72 -0
- data/user_stories/todo/sonority-identification.md +67 -0
- metadata +51 -6
- data/TODO.md +0 -218
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## Instrument Variant Refactoring
|
|
2
|
+
|
|
3
|
+
### Background
|
|
4
|
+
The current architecture conflates instrument catalog data with specific instrument instances. We need to separate these concerns to better represent how instruments are actually used in musical scores.
|
|
5
|
+
|
|
6
|
+
### Hierarchy
|
|
7
|
+
The refactored hierarchy will provide three levels of abstraction:
|
|
8
|
+
- **InstrumentFamily** (existing): Broad category like "saxophone" or "trumpet"
|
|
9
|
+
- **InstrumentType** (renamed from Instrument): Catalog entry with all possible variants (e.g., "trumpet" which can be in Bb, C, D, Eb)
|
|
10
|
+
- **Instrument** (new): Specific instance with selected variant (e.g., "Trumpet in C")
|
|
11
|
+
|
|
12
|
+
### User Stories
|
|
13
|
+
|
|
14
|
+
**STORY 1: Rename Instrument to InstrumentType**
|
|
15
|
+
AS a developer
|
|
16
|
+
WHEN I want to access instrument catalog data
|
|
17
|
+
I WANT to use InstrumentType.get("trumpet")
|
|
18
|
+
SO THAT it's clear I'm getting a type definition, not a specific instrument instance
|
|
19
|
+
|
|
20
|
+
**STORY 2: Create new Instrument class for specific variants**
|
|
21
|
+
AS a developer
|
|
22
|
+
WHEN I need a specific instrument for a score
|
|
23
|
+
I WANT to call Instrument.get("trumpet_in_c") or Instrument.get("trumpet", "in_c")
|
|
24
|
+
SO THAT I get a specific, usable instrument instance with proper transposition
|
|
25
|
+
|
|
26
|
+
**STORY 3: Instrument instances are sortable**
|
|
27
|
+
AS a developer
|
|
28
|
+
WHEN I have multiple Instrument instances in a score
|
|
29
|
+
I WANT them to sort properly by orchestral order and transposition
|
|
30
|
+
SO THAT "Trumpet in Eb" appears before "Trumpet in C" in the score
|
|
31
|
+
|
|
32
|
+
**STORY 4: Clear API for common use cases**
|
|
33
|
+
AS a developer
|
|
34
|
+
WHEN I create an Instrument without specifying a variant
|
|
35
|
+
I WANT to get the default variant automatically
|
|
36
|
+
SO THAT Instrument.get("clarinet") returns a Bb clarinet (the default)
|
|
37
|
+
|
|
38
|
+
**STORY 5: Instrument provides unified interface**
|
|
39
|
+
AS a developer
|
|
40
|
+
WHEN I have an Instrument instance
|
|
41
|
+
I WANT to access properties like name, transposition, clefs, and pitch_designation
|
|
42
|
+
SO THAT I don't need to navigate between instrument type and variant objects
|
|
43
|
+
|
|
44
|
+
### Implementation Notes
|
|
45
|
+
|
|
46
|
+
1. The Instrument class should:
|
|
47
|
+
- Wrap both an InstrumentType and a specific Variant
|
|
48
|
+
- Generate appropriate display names (e.g., "Clarinet in A")
|
|
49
|
+
- Provide methods for transposition, clefs, staff schemes
|
|
50
|
+
- Be directly usable in scores and parts
|
|
51
|
+
|
|
52
|
+
2. Factory methods should support:
|
|
53
|
+
- `Instrument.get("trumpet_in_c")` - parse variant from name
|
|
54
|
+
- `Instrument.get("trumpet", "in_c")` - explicit variant
|
|
55
|
+
- `Instrument.get("trumpet")` - use default variant
|
|
56
|
+
|
|
57
|
+
3. ScoreOrder should work with Instrument instances directly
|
|
58
|
+
|
|
59
|
+
### Migration Path
|
|
60
|
+
|
|
61
|
+
1. Rename existing Instrument class to InstrumentType
|
|
62
|
+
2. Update all references to use InstrumentType where appropriate
|
|
63
|
+
3. Create new Instrument class for variant instances
|
|
64
|
+
4. Update ScoreOrder to work with new Instrument instances
|
|
65
|
+
5. Update documentation and tests
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
IN ORDER TO accurately model sound events
|
|
2
|
+
AS a developer
|
|
3
|
+
I WANT a clear way to group the notion of a Note (pitch + rhythmic value) and an unpitched note.
|
|
4
|
+
|
|
5
|
+
We need a hierarchy of classes
|
|
6
|
+
|
|
7
|
+
class RhythmicEvent
|
|
8
|
+
attr_accessor :rhythmic_value
|
|
9
|
+
|
|
10
|
+
class Note < RhythmicEvent
|
|
11
|
+
attr_accessor :pitch
|
|
12
|
+
def sounded?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class UnpitchedNote < RhythmicEvent
|
|
17
|
+
def sounded?
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Rest < RhythmicEvent
|
|
22
|
+
def sounded?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
acceptance criteria
|
|
28
|
+
- the above class hierarchy and implementation requirements
|
|
29
|
+
- full test coverage
|
|
30
|
+
- use NotImplementedError instead of NotImplementedError in RhythmicEvent if and where appropriate.
|
|
@@ -0,0 +1,57 @@
|
|
|
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"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Dyad Analysis
|
|
2
|
+
|
|
3
|
+
As a music analyst or counterpoint student
|
|
4
|
+
|
|
5
|
+
I want to analyze two-note combinations (dyads)
|
|
6
|
+
|
|
7
|
+
So that I can understand harmonic implications in two-part music
|
|
8
|
+
|
|
9
|
+
Constructor should accept to pitches (or pitch classes) and an optional key
|
|
10
|
+
|
|
11
|
+
## Scenario: Identify interval in dyad
|
|
12
|
+
|
|
13
|
+
Given I have two pitches forming a dyad
|
|
14
|
+
|
|
15
|
+
When I access the interval property
|
|
16
|
+
|
|
17
|
+
Then I should receive the correct interval between the pitches
|
|
18
|
+
|
|
19
|
+
## Scenario: List possible triads from fifth
|
|
20
|
+
|
|
21
|
+
Given I have a dyad forming a perfect fifth
|
|
22
|
+
|
|
23
|
+
When I request possible triads
|
|
24
|
+
|
|
25
|
+
Then I should receive both major and minor triad options
|
|
26
|
+
|
|
27
|
+
And each should contain the given pitches
|
|
28
|
+
|
|
29
|
+
## Scenario: List possible triads from third
|
|
30
|
+
|
|
31
|
+
Given I have a dyad forming a minor third
|
|
32
|
+
|
|
33
|
+
When I request possible triads
|
|
34
|
+
|
|
35
|
+
Then I should receive minor and diminished triad options
|
|
36
|
+
|
|
37
|
+
And for a major third I should receive major and augmented options
|
|
38
|
+
|
|
39
|
+
## Scenario: Find possible seventh chords
|
|
40
|
+
|
|
41
|
+
Given I have a dyad
|
|
42
|
+
|
|
43
|
+
When I request possible seventh chords
|
|
44
|
+
|
|
45
|
+
Then I should receive all seventh chords containing those pitches
|
|
46
|
+
|
|
47
|
+
And they should include appropriate inversions
|
|
48
|
+
|
|
49
|
+
## Scenario: Handle enharmonic possibilities
|
|
50
|
+
|
|
51
|
+
Given I have a dyad with enharmonic possibilities
|
|
52
|
+
|
|
53
|
+
When I request possible enharmonic chords
|
|
54
|
+
|
|
55
|
+
Then I should receive chords based on enharmonic respellings
|
|
56
|
+
|
|
57
|
+
And each should be correctly identified
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
Project = overall container.
|
|
3
|
+
Flow = segment-level unit (supports sketches, movements, cues).
|
|
4
|
+
Sequence = neutral term for the editing canvas (2D space).
|
|
5
|
+
Timeline = strictly the axis.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Material?
|
|
9
|
+
|
|
10
|
+
ScorePart
|
|
11
|
+
@name
|
|
12
|
+
>> instruments
|
|
13
|
+
def primary_instrument
|
|
14
|
+
def default_staff_system
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
MusicContent
|
|
18
|
+
>> score_parts
|
|
19
|
+
@score_type (orchestral, band, chamber, pop, solo)
|
|
20
|
+
def ordered_score_parts
|
|
21
|
+
def score_parts_grouped_by_orchestra_section
|
|
22
|
+
# so we can square-bracket the sections
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Score < MusicContent
|
|
27
|
+
@title
|
|
28
|
+
@subtitle
|
|
29
|
+
@dedication
|
|
30
|
+
>> score_credits
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
ScoreCredit
|
|
34
|
+
> person
|
|
35
|
+
- role (composer, songwriter, lyricist, arranger, transcriber)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Person
|
|
39
|
+
- full_name
|
|
40
|
+
- birth_year int optional
|
|
41
|
+
- death_year int optional
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ScoreLayout < Layout
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
EnsembleSession (rehearsal, recording, or performance)
|
|
49
|
+
>> scores
|
|
50
|
+
>> score_part_players
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
ScorePartPlayer
|
|
54
|
+
> score_part
|
|
55
|
+
>> players
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
Player
|
|
61
|
+
> person
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Fragment < MusicContent
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Create a PercussionSet staff for percussion or drum kit parts in a score.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Pitch Class Set Analysis
|
|
2
|
+
|
|
3
|
+
As a music theorist studying atonal music
|
|
4
|
+
|
|
5
|
+
I want to analyze pitch class sets
|
|
6
|
+
|
|
7
|
+
So that I can identify set relationships and transformations
|
|
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
|
+
## Scenario: Find normal form
|
|
50
|
+
|
|
51
|
+
Given I have a pitch class set
|
|
52
|
+
|
|
53
|
+
When I request the normal form
|
|
54
|
+
|
|
55
|
+
Then I should receive the most compact rotation
|
|
56
|
+
|
|
57
|
+
And it should minimize the interval span
|
|
58
|
+
|
|
59
|
+
## Scenario: Find prime form
|
|
60
|
+
|
|
61
|
+
Given I have a pitch class set
|
|
62
|
+
|
|
63
|
+
When I request the prime form
|
|
64
|
+
|
|
65
|
+
Then I should receive the most compact form
|
|
66
|
+
|
|
67
|
+
And it should consider both the original and all inversions
|
|
68
|
+
|
|
69
|
+
And it should be the optimal normal form among all possibilities
|
|
70
|
+
|
|
71
|
+
## Scenario: Compare equivalent sets
|
|
72
|
+
|
|
73
|
+
Given I have two different pitch class sets
|
|
74
|
+
|
|
75
|
+
When I compare their prime forms
|
|
76
|
+
|
|
77
|
+
Then I can determine if they are equivalent
|
|
78
|
+
|
|
79
|
+
And identify the transformation relationship between them
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
Note: A PitchSet is unlike a PitchClassSet in that the pitches have spellings with octaves rather than Spellings only or octave-less 0-11 designations.
|
|
10
|
+
|
|
11
|
+
## Scenario: Get size of pitch set
|
|
12
|
+
|
|
13
|
+
Given I have a pitch set with N pitches
|
|
14
|
+
|
|
15
|
+
When I call the size method
|
|
16
|
+
|
|
17
|
+
Then it should return the number of pitches in the set
|
|
18
|
+
|
|
19
|
+
## Scenario: Identify empty set
|
|
20
|
+
|
|
21
|
+
Given I have no pitches
|
|
22
|
+
|
|
23
|
+
When I create a pitch set
|
|
24
|
+
|
|
25
|
+
Then it should be identified as an EmptySet
|
|
26
|
+
|
|
27
|
+
## Scenario: Identify monad
|
|
28
|
+
|
|
29
|
+
Given I have a single pitch
|
|
30
|
+
|
|
31
|
+
When I check the pitch set type
|
|
32
|
+
|
|
33
|
+
Then it should be identified as a Monad
|
|
34
|
+
|
|
35
|
+
And monad? should return true
|
|
36
|
+
|
|
37
|
+
## Scenario: Identify dyad
|
|
38
|
+
|
|
39
|
+
Given I have exactly two pitches
|
|
40
|
+
|
|
41
|
+
When I check the pitch set type
|
|
42
|
+
|
|
43
|
+
Then it should be identified as a Dyad
|
|
44
|
+
|
|
45
|
+
And dyad? should return true
|
|
46
|
+
|
|
47
|
+
## Scenario: Distinguish triads from trichords
|
|
48
|
+
|
|
49
|
+
Given I have three pitches
|
|
50
|
+
|
|
51
|
+
When I analyze the pitch set
|
|
52
|
+
|
|
53
|
+
Then trichord? should return true for any 3-pitch set
|
|
54
|
+
|
|
55
|
+
And triad? should return true only if they form stacked thirds
|
|
56
|
+
|
|
57
|
+
## Scenario: Identify larger pitch sets
|
|
58
|
+
|
|
59
|
+
Given I have a pitch set with N pitches
|
|
60
|
+
|
|
61
|
+
When I check the classification
|
|
62
|
+
|
|
63
|
+
Then it should be identified as:
|
|
64
|
+
- Tetrachord (4 pitches) with seventh_chord? check
|
|
65
|
+
- Pentachord (5 pitches)
|
|
66
|
+
- Hexachord (6 pitches)
|
|
67
|
+
- Heptachord (7 pitches)
|
|
68
|
+
- Octachord (8 pitches)
|
|
69
|
+
- Nonachord (9 pitches)
|
|
70
|
+
- Decachord (10 pitches)
|
|
71
|
+
- Undecachord (11 pitches)
|
|
72
|
+
- Dodecachord (12 pitches)
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
|
48
|
+
|
|
49
|
+
## Scenario: Work with triads
|
|
50
|
+
|
|
51
|
+
Given I need to analyze triadic harmony
|
|
52
|
+
|
|
53
|
+
When I work with Triad objects
|
|
54
|
+
|
|
55
|
+
Then I should be able to identify major, minor, diminished, and augmented triads
|
|
56
|
+
|
|
57
|
+
And access their specific properties and methods
|
|
58
|
+
|
|
59
|
+
## Scenario: Work with seventh chords
|
|
60
|
+
|
|
61
|
+
Given I need to analyze seventh chord harmony
|
|
62
|
+
|
|
63
|
+
When I work with SeventhChord objects
|
|
64
|
+
|
|
65
|
+
Then I should be able to identify all common seventh chord types
|
|
66
|
+
|
|
67
|
+
And note that nothing beyond seventh chords is needed to analyze pre-Romantic music
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: head_music
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 9.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rob Head
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -86,14 +86,14 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
89
|
+
version: '2.0'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
96
|
+
version: '2.0'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: bundler-audit
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -144,16 +144,19 @@ files:
|
|
|
144
144
|
- ".ruby-version"
|
|
145
145
|
- ".yardopts"
|
|
146
146
|
- CHANGELOG.md
|
|
147
|
+
- CLAUDE.md
|
|
147
148
|
- CODE_OF_CONDUCT.md
|
|
148
149
|
- CONTRIBUTING.md
|
|
149
150
|
- Gemfile
|
|
150
151
|
- Gemfile.lock
|
|
151
152
|
- LICENSE.txt
|
|
153
|
+
- MUSIC_THEORY.md
|
|
152
154
|
- README.md
|
|
153
155
|
- Rakefile
|
|
154
|
-
-
|
|
156
|
+
- bin/check_instrument_consistency.rb
|
|
155
157
|
- bin/console
|
|
156
158
|
- bin/setup
|
|
159
|
+
- check_instrument_consistency.rb
|
|
157
160
|
- head_music.gemspec
|
|
158
161
|
- lib/head_music.rb
|
|
159
162
|
- lib/head_music/analysis/circle.rb
|
|
@@ -164,6 +167,7 @@ files:
|
|
|
164
167
|
- lib/head_music/analysis/diatonic_interval/semitones.rb
|
|
165
168
|
- lib/head_music/analysis/diatonic_interval/size.rb
|
|
166
169
|
- lib/head_music/analysis/harmonic_interval.rb
|
|
170
|
+
- lib/head_music/analysis/interval_consonance.rb
|
|
167
171
|
- lib/head_music/analysis/interval_cycle.rb
|
|
168
172
|
- lib/head_music/analysis/melodic_interval.rb
|
|
169
173
|
- lib/head_music/analysis/motion.rb
|
|
@@ -175,13 +179,15 @@ files:
|
|
|
175
179
|
- lib/head_music/content/note.rb
|
|
176
180
|
- lib/head_music/content/placement.rb
|
|
177
181
|
- lib/head_music/content/position.rb
|
|
178
|
-
- lib/head_music/content/rhythmic_value.rb
|
|
179
182
|
- lib/head_music/content/staff.rb
|
|
180
183
|
- lib/head_music/content/voice.rb
|
|
181
184
|
- lib/head_music/instruments/instrument.rb
|
|
182
185
|
- lib/head_music/instruments/instrument_families.yml
|
|
183
186
|
- lib/head_music/instruments/instrument_family.rb
|
|
187
|
+
- lib/head_music/instruments/instrument_type.rb
|
|
184
188
|
- lib/head_music/instruments/instruments.yml
|
|
189
|
+
- lib/head_music/instruments/score_order.rb
|
|
190
|
+
- lib/head_music/instruments/score_orders.yml
|
|
185
191
|
- lib/head_music/instruments/staff.rb
|
|
186
192
|
- lib/head_music/instruments/staff_scheme.rb
|
|
187
193
|
- lib/head_music/instruments/variant.rb
|
|
@@ -194,31 +200,49 @@ files:
|
|
|
194
200
|
- lib/head_music/locales/ru.yml
|
|
195
201
|
- lib/head_music/named.rb
|
|
196
202
|
- lib/head_music/rudiment/alteration.rb
|
|
203
|
+
- lib/head_music/rudiment/base.rb
|
|
197
204
|
- lib/head_music/rudiment/chromatic_interval.rb
|
|
198
205
|
- lib/head_music/rudiment/clef.rb
|
|
199
206
|
- lib/head_music/rudiment/clefs.yml
|
|
200
207
|
- lib/head_music/rudiment/consonance.rb
|
|
208
|
+
- lib/head_music/rudiment/diatonic_context.rb
|
|
209
|
+
- lib/head_music/rudiment/key.rb
|
|
201
210
|
- lib/head_music/rudiment/key_signature.rb
|
|
202
211
|
- lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb
|
|
203
212
|
- lib/head_music/rudiment/letter_name.rb
|
|
204
213
|
- lib/head_music/rudiment/meter.rb
|
|
214
|
+
- lib/head_music/rudiment/mode.rb
|
|
205
215
|
- lib/head_music/rudiment/musical_symbol.rb
|
|
216
|
+
- lib/head_music/rudiment/note.rb
|
|
206
217
|
- lib/head_music/rudiment/pitch.rb
|
|
207
218
|
- lib/head_music/rudiment/pitch/enharmonic_equivalence.rb
|
|
208
219
|
- lib/head_music/rudiment/pitch/octave_equivalence.rb
|
|
220
|
+
- lib/head_music/rudiment/pitch/parser.rb
|
|
209
221
|
- lib/head_music/rudiment/pitch_class.rb
|
|
210
222
|
- lib/head_music/rudiment/quality.rb
|
|
211
223
|
- lib/head_music/rudiment/reference_pitch.rb
|
|
212
224
|
- lib/head_music/rudiment/register.rb
|
|
225
|
+
- lib/head_music/rudiment/rest.rb
|
|
213
226
|
- lib/head_music/rudiment/rhythm.rb
|
|
227
|
+
- lib/head_music/rudiment/rhythmic_element.rb
|
|
214
228
|
- lib/head_music/rudiment/rhythmic_unit.rb
|
|
229
|
+
- lib/head_music/rudiment/rhythmic_unit/parser.rb
|
|
230
|
+
- lib/head_music/rudiment/rhythmic_units.yml
|
|
231
|
+
- lib/head_music/rudiment/rhythmic_value.rb
|
|
232
|
+
- lib/head_music/rudiment/rhythmic_value/parser.rb
|
|
215
233
|
- lib/head_music/rudiment/scale.rb
|
|
216
234
|
- lib/head_music/rudiment/scale_degree.rb
|
|
217
235
|
- lib/head_music/rudiment/scale_type.rb
|
|
218
236
|
- lib/head_music/rudiment/solmization.rb
|
|
219
237
|
- lib/head_music/rudiment/solmizations.yml
|
|
220
238
|
- lib/head_music/rudiment/spelling.rb
|
|
239
|
+
- lib/head_music/rudiment/tempo.rb
|
|
240
|
+
- lib/head_music/rudiment/tonal_context.rb
|
|
221
241
|
- lib/head_music/rudiment/tuning.rb
|
|
242
|
+
- lib/head_music/rudiment/tuning/just_intonation.rb
|
|
243
|
+
- lib/head_music/rudiment/tuning/meantone.rb
|
|
244
|
+
- lib/head_music/rudiment/tuning/pythagorean.rb
|
|
245
|
+
- lib/head_music/rudiment/unpitched_note.rb
|
|
222
246
|
- lib/head_music/style/analysis.rb
|
|
223
247
|
- lib/head_music/style/annotation.rb
|
|
224
248
|
- lib/head_music/style/guidelines/always_move.rb
|
|
@@ -259,9 +283,30 @@ files:
|
|
|
259
283
|
- lib/head_music/style/guides/fux_cantus_firmus.rb
|
|
260
284
|
- lib/head_music/style/guides/modern_cantus_firmus.rb
|
|
261
285
|
- lib/head_music/style/mark.rb
|
|
286
|
+
- lib/head_music/style/medieval_tradition.rb
|
|
287
|
+
- lib/head_music/style/modern_tradition.rb
|
|
288
|
+
- lib/head_music/style/renaissance_tradition.rb
|
|
289
|
+
- lib/head_music/style/tradition.rb
|
|
262
290
|
- lib/head_music/utilities/hash_key.rb
|
|
263
291
|
- lib/head_music/version.rb
|
|
264
292
|
- test_translations.rb
|
|
293
|
+
- user_stories/active/handle-time.md
|
|
294
|
+
- user_stories/active/handle-time.rb
|
|
295
|
+
- user_stories/done/epic--score-order/PLAN.md
|
|
296
|
+
- user_stories/done/epic--score-order/band-score-order.md
|
|
297
|
+
- user_stories/done/epic--score-order/chamber-ensemble-score-order.md
|
|
298
|
+
- user_stories/done/epic--score-order/orchestral-score-order.md
|
|
299
|
+
- user_stories/done/instrument-variant.md
|
|
300
|
+
- user_stories/done/superclass-for-note.md
|
|
301
|
+
- user_stories/todo/agentic-daw.md
|
|
302
|
+
- user_stories/todo/consonance-dissonance-classification.md
|
|
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
|
|
265
310
|
homepage: https://github.com/roberthead/head_music
|
|
266
311
|
licenses:
|
|
267
312
|
- MIT
|