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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 012ce64dd4174cbeec42f1d4825e9fd10af79ed8f4b83a3beedbc14a9aea34fa
|
|
4
|
+
data.tar.gz: a31dbb0b6b3d732bd41e1dbfe6a042a8395db05832d58848ec4d53e5ae4a4524
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a98367425dcfb4899b24ef2e7986a7fbc594202c960b553ab35d3cecd5ee0b5ecb10f65e1e458fe2d1797b70eded378fd319b2cbfc7b9a5a2e393773ce75f06b
|
|
7
|
+
data.tar.gz: d3baf95b28de6c2df0e3d14250ed9c335cb3793d598eb7cf1c417dd40b6463bb57ddcda19be309a1045e835809ad3b9ad45dc0fd21797563319069da35db76bc
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [9.0.0] - 2025-10-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `HeadMusic::Rudiment::Pitch::Parser` for strict pitch parsing
|
|
14
|
+
- Added `HeadMusic::Rudiment::RhythmicValue::Parser` for rhythmic value parsing
|
|
15
|
+
- Both parsers provide standardized `.parse()` class method API
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `Pitch.from_name` now uses `Pitch::Parser` internally
|
|
19
|
+
- `RhythmicValue.get` now uses `RhythmicValue::Parser` internally
|
|
20
|
+
- `Note.get` now parses "pitch rhythmic_value" strings inline without Parse module
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
- **BREAKING**: Removed `HeadMusic::Parse::Pitch` class
|
|
24
|
+
- **BREAKING**: Removed `HeadMusic::Parse::RhythmicValue` class
|
|
25
|
+
- **BREAKING**: Removed `HeadMusic::Parse::RhythmicElement` class
|
|
26
|
+
- **BREAKING**: Removed entire `HeadMusic::Parse` module
|
|
27
|
+
|
|
28
|
+
### Migration Guide
|
|
29
|
+
|
|
30
|
+
If you were using the removed Parse classes, migrate as follows:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Before (v8.x)
|
|
34
|
+
parser = HeadMusic::Parse::Pitch.new("C#4")
|
|
35
|
+
pitch = parser.pitch
|
|
36
|
+
|
|
37
|
+
# After (v9.x)
|
|
38
|
+
pitch = HeadMusic::Rudiment::Pitch.get("C#4")
|
|
39
|
+
# or for strict parsing:
|
|
40
|
+
pitch = HeadMusic::Rudiment::Pitch::Parser.parse("C#4")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# Before (v8.x)
|
|
45
|
+
parser = HeadMusic::Parse::RhythmicValue.new("dotted quarter")
|
|
46
|
+
value = parser.rhythmic_value
|
|
47
|
+
|
|
48
|
+
# After (v9.x)
|
|
49
|
+
value = HeadMusic::Rudiment::RhythmicValue.get("dotted quarter")
|
|
50
|
+
# or for strict parsing:
|
|
51
|
+
value = HeadMusic::Rudiment::RhythmicValue::Parser.parse("dotted quarter")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# Before (v8.x)
|
|
56
|
+
parser = HeadMusic::Parse::RhythmicElement.new("F#4 dotted-quarter")
|
|
57
|
+
note = parser.note
|
|
58
|
+
|
|
59
|
+
# After (v9.x)
|
|
60
|
+
note = HeadMusic::Rudiment::Note.get("F#4 dotted-quarter")
|
|
61
|
+
```
|
|
62
|
+
|
|
10
63
|
## [8.2.1] - 2025-06-21
|
|
11
64
|
|
|
12
65
|
### Added
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
HeadMusic is a Ruby gem for Western music theory. It provides a comprehensive toolkit for working with pitches, scales, intervals, chords, and musical analysis. The gem supports internationalization with translations in 7 languages.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
### Essential Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Install dependencies
|
|
15
|
+
bin/setup
|
|
16
|
+
|
|
17
|
+
# Run tests with coverage
|
|
18
|
+
bundle exec rake
|
|
19
|
+
|
|
20
|
+
# Run tests without coverage
|
|
21
|
+
bundle exec rspec
|
|
22
|
+
|
|
23
|
+
# Run a specific test file
|
|
24
|
+
bundle exec rspec spec/head_music/rudiments/pitch_spec.rb
|
|
25
|
+
|
|
26
|
+
# Run linting
|
|
27
|
+
bundle exec rubocop
|
|
28
|
+
|
|
29
|
+
# Run all quality checks (tests, linting, security)
|
|
30
|
+
bundle exec rake quality
|
|
31
|
+
|
|
32
|
+
# Open interactive console with gem loaded
|
|
33
|
+
bin/console
|
|
34
|
+
# or
|
|
35
|
+
bundle exec rake console
|
|
36
|
+
|
|
37
|
+
# Generate documentation
|
|
38
|
+
bundle exec rake doc
|
|
39
|
+
|
|
40
|
+
# Check documentation coverage
|
|
41
|
+
bundle exec rake doc_stats
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Git Etiquette
|
|
45
|
+
|
|
46
|
+
Do not make a commit unless I ask you to.
|
|
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.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle exec rubocop -a
|
|
58
|
+
```
|
|
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
|
+
|
|
64
|
+
### Testing
|
|
65
|
+
|
|
66
|
+
Tests are written in RSpec and located in the `/spec` directory, mirroring the `/lib` structure. The project requires 90% code coverage minimum.
|
|
67
|
+
|
|
68
|
+
## Architecture
|
|
69
|
+
|
|
70
|
+
### Module Structure
|
|
71
|
+
|
|
72
|
+
The codebase follows a domain-driven design with clear module boundaries:
|
|
73
|
+
|
|
74
|
+
1. **HeadMusic::Rudiment** - Core music theory elements
|
|
75
|
+
- Pitch, Note, Scale, Key, Interval, Chord
|
|
76
|
+
- Factory methods: `.get()` for most rudiments
|
|
77
|
+
|
|
78
|
+
2. **HeadMusic::Analysis** - Musical analysis tools
|
|
79
|
+
- Intervals, Chords, Motion analysis
|
|
80
|
+
- Harmonic and melodic analysis
|
|
81
|
+
|
|
82
|
+
3. **HeadMusic::Content** - Musical composition representation
|
|
83
|
+
- Composition, Voice, Note, Rest
|
|
84
|
+
- Rhythmic values and time signatures
|
|
85
|
+
|
|
86
|
+
4. **HeadMusic::Instruments** - Instrument definitions
|
|
87
|
+
- Instrument families and properties
|
|
88
|
+
- Pitch ranges and transposition
|
|
89
|
+
|
|
90
|
+
5. **HeadMusic::Style** - Composition rules and guidelines
|
|
91
|
+
- Counterpoint rules
|
|
92
|
+
- Voice leading guidelines
|
|
93
|
+
- Style analysis
|
|
94
|
+
|
|
95
|
+
### Key Design Patterns
|
|
96
|
+
|
|
97
|
+
- **Factory Pattern**: Most musical objects use `.get()` factory methods
|
|
98
|
+
- **Value Objects**: Immutable objects for musical concepts
|
|
99
|
+
- **Named Mixin**: Provides internationalization support
|
|
100
|
+
- **Delegation**: Extensive use of delegation for clean APIs
|
|
101
|
+
|
|
102
|
+
### Entry Points
|
|
103
|
+
|
|
104
|
+
- Main file: `lib/head_music.rb`
|
|
105
|
+
- Module loading order is important and defined in the main file
|
|
106
|
+
- Constants like GOLDEN_RATIO are defined at the top level
|
|
107
|
+
|
|
108
|
+
## Important Implementation Details
|
|
109
|
+
|
|
110
|
+
### Internationalization
|
|
111
|
+
|
|
112
|
+
The gem supports multiple languages through the HeadMusic::Named mixin:
|
|
113
|
+
- Translations in `lib/head_music/locales/`
|
|
114
|
+
- Languages: en, de, es, fr, it, ja, nl
|
|
115
|
+
- Use `I18n.locale = :de` to change language
|
|
116
|
+
|
|
117
|
+
### Testing Patterns
|
|
118
|
+
|
|
119
|
+
- Use `described_class` instead of hardcoding class names
|
|
120
|
+
- Test files must end with `_spec.rb`
|
|
121
|
+
- Shared examples in `spec/support/`
|
|
122
|
+
- `composition_context.rb` provides test utilities
|
|
123
|
+
|
|
124
|
+
### Code Style
|
|
125
|
+
|
|
126
|
+
- Ruby 3.3.0+ features are allowed
|
|
127
|
+
- Follow Standard Ruby style guide
|
|
128
|
+
- Use YARD documentation format for public methods
|
|
129
|
+
- Prefer delegation over inheritance
|
|
130
|
+
- Always run `bundle exec rubocop -a` after editing ruby code
|
|
131
|
+
|
|
132
|
+
## Common Development Tasks
|
|
133
|
+
|
|
134
|
+
### Adding a New Musical Concept
|
|
135
|
+
|
|
136
|
+
1. Create the class in the appropriate module
|
|
137
|
+
2. Include `HeadMusic::Named` if it needs internationalization
|
|
138
|
+
3. Add factory method `.get()` if appropriate
|
|
139
|
+
4. Create corresponding spec file
|
|
140
|
+
5. Add translations to locale files if using Named
|
|
141
|
+
|
|
142
|
+
### Modifying Existing Classes
|
|
143
|
+
|
|
144
|
+
1. Check for dependent classes that might be affected
|
|
145
|
+
2. Run tests for the specific module: `bundle exec rspec spec/head_music/[module_name]`
|
|
146
|
+
3. Update documentation with YARD comments
|
|
147
|
+
4. Ensure translations are updated if names change
|
|
148
|
+
|
|
149
|
+
## Music theory and concepts
|
|
150
|
+
|
|
151
|
+
Please refer to MUSIC_THEORY.md for music theory domain knowledge.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
head_music (
|
|
4
|
+
head_music (9.0.0)
|
|
5
5
|
activesupport (~> 7.0)
|
|
6
6
|
humanize (~> 2.0)
|
|
7
7
|
i18n (~> 1.8)
|
|
@@ -22,54 +22,54 @@ GEM
|
|
|
22
22
|
securerandom (>= 0.3)
|
|
23
23
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
24
24
|
ast (2.4.3)
|
|
25
|
-
base64 (0.
|
|
26
|
-
benchmark (0.4.
|
|
27
|
-
bigdecimal (3.
|
|
25
|
+
base64 (0.3.0)
|
|
26
|
+
benchmark (0.4.1)
|
|
27
|
+
bigdecimal (3.2.2)
|
|
28
28
|
bundler-audit (0.9.2)
|
|
29
29
|
bundler (>= 1.2.0, < 3)
|
|
30
30
|
thor (~> 1.0)
|
|
31
31
|
concurrent-ruby (1.3.5)
|
|
32
32
|
connection_pool (2.5.3)
|
|
33
|
-
diff-lcs (1.6.
|
|
33
|
+
diff-lcs (1.6.2)
|
|
34
34
|
docile (1.4.1)
|
|
35
|
-
drb (2.2.
|
|
35
|
+
drb (2.2.3)
|
|
36
36
|
humanize (2.5.1)
|
|
37
37
|
i18n (1.14.7)
|
|
38
38
|
concurrent-ruby (~> 1.0)
|
|
39
|
-
json (2.
|
|
39
|
+
json (2.13.1)
|
|
40
40
|
kramdown (2.5.1)
|
|
41
41
|
rexml (>= 3.3.9)
|
|
42
|
-
language_server-protocol (3.17.0.
|
|
42
|
+
language_server-protocol (3.17.0.5)
|
|
43
43
|
lint_roller (1.1.0)
|
|
44
44
|
logger (1.7.0)
|
|
45
45
|
minitest (5.25.5)
|
|
46
46
|
parallel (1.27.0)
|
|
47
|
-
parser (3.3.
|
|
47
|
+
parser (3.3.9.0)
|
|
48
48
|
ast (~> 2.4.1)
|
|
49
49
|
racc
|
|
50
50
|
prism (1.4.0)
|
|
51
51
|
racc (1.8.1)
|
|
52
52
|
rainbow (3.1.1)
|
|
53
|
-
rake (13.
|
|
53
|
+
rake (13.3.0)
|
|
54
54
|
regexp_parser (2.10.0)
|
|
55
55
|
rexml (3.4.1)
|
|
56
|
-
rspec (3.13.
|
|
56
|
+
rspec (3.13.1)
|
|
57
57
|
rspec-core (~> 3.13.0)
|
|
58
58
|
rspec-expectations (~> 3.13.0)
|
|
59
59
|
rspec-mocks (~> 3.13.0)
|
|
60
|
-
rspec-core (3.13.
|
|
60
|
+
rspec-core (3.13.5)
|
|
61
61
|
rspec-support (~> 3.13.0)
|
|
62
|
-
rspec-expectations (3.13.
|
|
62
|
+
rspec-expectations (3.13.5)
|
|
63
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
64
64
|
rspec-support (~> 3.13.0)
|
|
65
|
-
rspec-its (
|
|
66
|
-
rspec-core (>= 3.
|
|
67
|
-
rspec-expectations (>= 3.
|
|
68
|
-
rspec-mocks (3.13.
|
|
65
|
+
rspec-its (2.0.0)
|
|
66
|
+
rspec-core (>= 3.13.0)
|
|
67
|
+
rspec-expectations (>= 3.13.0)
|
|
68
|
+
rspec-mocks (3.13.5)
|
|
69
69
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
70
70
|
rspec-support (~> 3.13.0)
|
|
71
|
-
rspec-support (3.13.
|
|
72
|
-
rubocop (1.75.
|
|
71
|
+
rspec-support (3.13.4)
|
|
72
|
+
rubocop (1.75.8)
|
|
73
73
|
json (~> 2.3)
|
|
74
74
|
language_server-protocol (~> 3.17.0.2)
|
|
75
75
|
lint_roller (~> 1.1.0)
|
|
@@ -80,7 +80,7 @@ GEM
|
|
|
80
80
|
rubocop-ast (>= 1.44.0, < 2.0)
|
|
81
81
|
ruby-progressbar (~> 1.7)
|
|
82
82
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
83
|
-
rubocop-ast (1.
|
|
83
|
+
rubocop-ast (1.46.0)
|
|
84
84
|
parser (>= 3.3.7.2)
|
|
85
85
|
prism (~> 1.4)
|
|
86
86
|
rubocop-performance (1.25.0)
|
|
@@ -99,12 +99,12 @@ GEM
|
|
|
99
99
|
docile (~> 1.1)
|
|
100
100
|
simplecov-html (~> 0.11)
|
|
101
101
|
simplecov_json_formatter (~> 0.1)
|
|
102
|
-
simplecov-html (0.13.
|
|
102
|
+
simplecov-html (0.13.2)
|
|
103
103
|
simplecov_json_formatter (0.1.4)
|
|
104
|
-
standard (1.
|
|
104
|
+
standard (1.50.0)
|
|
105
105
|
language_server-protocol (~> 3.17.0.2)
|
|
106
106
|
lint_roller (~> 1.0)
|
|
107
|
-
rubocop (~> 1.75.
|
|
107
|
+
rubocop (~> 1.75.5)
|
|
108
108
|
standard-custom (~> 1.0.0)
|
|
109
109
|
standard-performance (~> 1.8)
|
|
110
110
|
standard-custom (1.0.2)
|
|
@@ -113,7 +113,7 @@ GEM
|
|
|
113
113
|
standard-performance (1.8.0)
|
|
114
114
|
lint_roller (~> 1.1)
|
|
115
115
|
rubocop-performance (~> 1.25.0)
|
|
116
|
-
thor (1.
|
|
116
|
+
thor (1.4.0)
|
|
117
117
|
tzinfo (2.0.6)
|
|
118
118
|
concurrent-ruby (~> 1.0)
|
|
119
119
|
unicode-display_width (3.1.4)
|
|
@@ -132,7 +132,7 @@ DEPENDENCIES
|
|
|
132
132
|
kramdown
|
|
133
133
|
rake (~> 13.0)
|
|
134
134
|
rspec (~> 3.0)
|
|
135
|
-
rspec-its (~>
|
|
135
|
+
rspec-its (~> 2.0)
|
|
136
136
|
rubocop
|
|
137
137
|
rubocop-rake
|
|
138
138
|
rubocop-rspec
|
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/Rakefile
CHANGED
|
@@ -12,7 +12,7 @@ begin
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
desc "Generate documentation and show stats"
|
|
15
|
-
task :
|
|
15
|
+
task doc_stats: :doc do
|
|
16
16
|
sh "yard stats --list-undoc"
|
|
17
17
|
end
|
|
18
18
|
rescue LoadError
|
|
@@ -29,7 +29,7 @@ end
|
|
|
29
29
|
task default: :spec
|
|
30
30
|
|
|
31
31
|
desc "Run all quality checks (tests, linting, security audit)"
|
|
32
|
-
task :
|
|
32
|
+
task quality: [:spec, :standard, "bundle:audit:check"]
|
|
33
33
|
|
|
34
34
|
desc "Open an irb session preloaded with this library"
|
|
35
35
|
task :console do
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
# Load the YAML files
|
|
4
|
+
instruments_file = "/Users/roberthead/github.com/roberthead/head_music/lib/head_music/instruments/instruments.yml"
|
|
5
|
+
families_file = "/Users/roberthead/github.com/roberthead/head_music/lib/head_music/instruments/instrument_families.yml"
|
|
6
|
+
|
|
7
|
+
instruments = YAML.load_file(instruments_file)
|
|
8
|
+
families = YAML.load_file(families_file)
|
|
9
|
+
|
|
10
|
+
puts "Checking for inconsistencies between instruments and their families...\n\n"
|
|
11
|
+
|
|
12
|
+
inconsistencies = []
|
|
13
|
+
|
|
14
|
+
instruments.each do |instrument_name, instrument_data|
|
|
15
|
+
family_key = instrument_data["family_key"]
|
|
16
|
+
next unless family_key
|
|
17
|
+
|
|
18
|
+
family_data = families[family_key]
|
|
19
|
+
next unless family_data
|
|
20
|
+
|
|
21
|
+
# Check orchestra_section_key consistency
|
|
22
|
+
instrument_section = instrument_data["orchestra_section_key"]
|
|
23
|
+
family_section = family_data["orchestra_section_key"]
|
|
24
|
+
|
|
25
|
+
if instrument_section && family_section && instrument_section != family_section
|
|
26
|
+
inconsistencies << {
|
|
27
|
+
instrument: instrument_name,
|
|
28
|
+
family: family_key,
|
|
29
|
+
field: "orchestra_section_key",
|
|
30
|
+
instrument_value: instrument_section,
|
|
31
|
+
family_value: family_section
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Check classification_keys consistency
|
|
36
|
+
instrument_classifications = instrument_data["classification_keys"] || []
|
|
37
|
+
family_classifications = family_data["classification_keys"] || []
|
|
38
|
+
|
|
39
|
+
# Check if instrument has classifications that conflict with family
|
|
40
|
+
if !instrument_classifications.empty? && !family_classifications.empty?
|
|
41
|
+
conflicts = instrument_classifications - family_classifications
|
|
42
|
+
if !conflicts.empty?
|
|
43
|
+
inconsistencies << {
|
|
44
|
+
instrument: instrument_name,
|
|
45
|
+
family: family_key,
|
|
46
|
+
field: "classification_keys",
|
|
47
|
+
instrument_value: instrument_classifications,
|
|
48
|
+
family_value: family_classifications,
|
|
49
|
+
conflicts: conflicts
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if inconsistencies.empty?
|
|
56
|
+
puts "No inconsistencies found!"
|
|
57
|
+
else
|
|
58
|
+
puts "Found #{inconsistencies.length} inconsistencies:\n\n"
|
|
59
|
+
|
|
60
|
+
inconsistencies.each_with_index do |issue, index|
|
|
61
|
+
puts "#{index + 1}. #{issue[:instrument]} (family: #{issue[:family]})"
|
|
62
|
+
puts " Field: #{issue[:field]}"
|
|
63
|
+
puts " Instrument value: #{issue[:instrument_value]}"
|
|
64
|
+
puts " Family value: #{issue[:family_value]}"
|
|
65
|
+
if issue[:conflicts]
|
|
66
|
+
puts " Conflicting classifications: #{issue[:conflicts]}"
|
|
67
|
+
end
|
|
68
|
+
puts ""
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Also check for instruments with orchestra_section_key but no family
|
|
73
|
+
puts "\nInstruments with orchestra_section_key but no family_key:"
|
|
74
|
+
instruments.each do |instrument_name, instrument_data|
|
|
75
|
+
if instrument_data["orchestra_section_key"] && !instrument_data["family_key"]
|
|
76
|
+
puts "- #{instrument_name}: #{instrument_data["orchestra_section_key"]}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Check for instruments with classification_keys but no family
|
|
81
|
+
puts "\nInstruments with classification_keys but no family_key:"
|
|
82
|
+
instruments.each do |instrument_name, instrument_data|
|
|
83
|
+
if instrument_data["classification_keys"] && !instrument_data["family_key"]
|
|
84
|
+
puts "- #{instrument_name}: #{instrument_data["classification_keys"]}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
File without changes
|
data/head_music.gemspec
CHANGED
|
@@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
|
|
39
39
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
40
40
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
41
|
-
spec.add_development_dependency "rspec-its", "~>
|
|
41
|
+
spec.add_development_dependency "rspec-its", "~> 2.0"
|
|
42
42
|
spec.add_development_dependency "bundler-audit", "~> 0.9"
|
|
43
43
|
spec.add_development_dependency "yard", "~> 0.9"
|
|
44
44
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Accepts a number and number of semitones and
|
|
1
|
+
# Accepts a number and number of semitones and provides the naming methods.
|
|
2
2
|
class HeadMusic::Analysis::DiatonicInterval::Naming
|
|
3
3
|
QUALITY_SEMITONES = HeadMusic::Analysis::DiatonicInterval::QUALITY_SEMITONES
|
|
4
4
|
NUMBER_NAMES = HeadMusic::Analysis::DiatonicInterval::NUMBER_NAMES
|