head_music 11.2.0 → 11.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 612ae51821bb032f85973fb28527749fdbc8e07291cdfcfbfd6936c204e21829
4
- data.tar.gz: 6b10642629a73a596b6100e53dde57b8a00cca92ba6fa340c21713e1955167d0
3
+ metadata.gz: 4d8882c692b8b956f3856e601c37ce271c86b513064071d69b8e6965d67d8514
4
+ data.tar.gz: 9c14060294dfc59bb6abc768f5090edec67bdb65ef3c8e4ad6c7079b4fec93df
5
5
  SHA512:
6
- metadata.gz: a26c1c53f68e7e0bf6623005877a5174d2cbf429faa8c08cbcf36ecaaaee8b114b04b1426d4eb20e1b661da17fafeae9c170e6370aef38455ac43180d4b2714e
7
- data.tar.gz: ca47a804546899e9c3fb057ef9de8908e15f2da2ea27a98b29926406c0bf4ecd9818ed08efc7177f92e2f0eeaf20cc4527f0a69afbae49ebc46e9e414f5c55a1
6
+ metadata.gz: f0803c09f157ec3331c3e3921c5fb9d3ec2873b7655a54f2d4dc518613d4059f470eaf17900c1f2f28fbd274fe15e4a53d7603c65f2b6dacd550dc596b57c872
7
+ data.tar.gz: f98d4b7e8fdfdae06ca3e116ebf19793151abaeeb5c3535708181abcb3b8d9deae965262da7f69fab6ca6e29065832f253532b186dfa0bfc0e98d8c4a80f2e90
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- head_music (11.2.0)
4
+ head_music (11.3.0)
5
5
  activesupport (>= 7.0, < 10)
6
6
  humanize (~> 2.0)
7
7
  i18n (~> 1.8)
@@ -0,0 +1,58 @@
1
+ module HeadMusic
2
+ module Content
3
+ module CantusFirmus
4
+ # Sample cantus firmus examples from various pedagogical sources.
5
+ # These are traditional melodies used for teaching counterpoint.
6
+ class Example
7
+ EXAMPLES_DATA = YAML.load_file(File.expand_path("examples.yml", __dir__)).freeze
8
+
9
+ attr_reader :source, :tonal_center, :mode, :pitches
10
+
11
+ class << self
12
+ def all
13
+ @all ||= EXAMPLES_DATA["cantus_firmus_examples"].map do |data|
14
+ new(data: data)
15
+ end
16
+ end
17
+
18
+ def by_source(source_identifier)
19
+ source = Source.get(source_identifier)
20
+ return [] unless source
21
+
22
+ all.select { |example| example.source == source }
23
+ end
24
+
25
+ def sources
26
+ all.map(&:source).uniq
27
+ end
28
+
29
+ def by_mode(mode_name)
30
+ normalized_mode = mode_name.to_s.downcase
31
+ all.select { |example| example.mode.to_s.downcase == normalized_mode }
32
+ end
33
+
34
+ def by_tonal_center(tonal_center_name)
35
+ all.select { |example| example.tonal_center.to_s == tonal_center_name.to_s }
36
+ end
37
+ end
38
+
39
+ def initialize(data:)
40
+ @source = Source.get(data["source"])
41
+ @tonal_center = data["tonal_center"]
42
+ @mode = data["mode"]&.to_sym
43
+ @pitches = data["pitches"] || []
44
+ end
45
+
46
+ def length
47
+ pitches.length
48
+ end
49
+
50
+ def to_s
51
+ "#{tonal_center} #{mode} (#{source})"
52
+ end
53
+
54
+ private_class_method :new
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,115 @@
1
+ cantus_firmus_examples:
2
+ - source: "Fux"
3
+ tonal_center: D
4
+ mode: dorian
5
+ pitches: ["D", "F", "E", "D", "G", "F", "A", "G", "F", "E", "D"]
6
+
7
+ - source: "Fux"
8
+ tonal_center: E
9
+ mode: phrygian
10
+ pitches: ["E", "C", "D", "C", "A3", "A", "G", "E", "F", "E"]
11
+
12
+ - source: "Fux"
13
+ tonal_center: F
14
+ mode: lydian
15
+ pitches: ["F", "G", "A", "F", "D", "E", "F", "C5", "A", "F", "G", "F"]
16
+
17
+ - source: "Fux"
18
+ tonal_center: G
19
+ mode: mixolydian
20
+ pitches: ["G3", "C", "B3", "G3", "C", "E", "D", "G", "E", "C", "D", "B3", "A3", "G3"]
21
+
22
+ - source: "Fux"
23
+ tonal_center: A
24
+ mode: aeolian
25
+ pitches: ["A3", "C", "B3", "D", "C", "E", "F", "E", "D", "C", "B3", "A3"]
26
+
27
+ - source: "Fux"
28
+ tonal_center: C
29
+ mode: ionian
30
+ pitches: ["C", "E", "F", "G", "E", "A", "G", "E", "F", "E", "D", "C"]
31
+
32
+ - source: "Fux"
33
+ tonal_center: C
34
+ mode: ionian
35
+ pitches: ["C", "E", "F", "E", "G", "F", "E", "D", "C"]
36
+
37
+ - source: "Clendinning & Marvin"
38
+ tonal_center: F
39
+ mode: ionian
40
+ pitches: ["F3", "G3", "A3", "F3", "D3", "E3", "F3", "C4", "A3", "F3", "G3", "F3"]
41
+
42
+ - source: "Clendinning & Marvin"
43
+ tonal_center: D
44
+ mode: aeolian
45
+ pitches: ["D3", "A3", "G3", "F3", "E3", "D3", "F3", "E3", "D3"]
46
+
47
+ - source: "Clendinning & Marvin"
48
+ tonal_center: C
49
+ mode: ionian
50
+ pitches: ["C", "D", "F", "E", "F", "G", "A", "G", "E", "D", "C"]
51
+
52
+ - source: "Clendinning & Marvin"
53
+ tonal_center: C
54
+ mode: ionian
55
+ pitches: ["C3", "E3", "F3", "G3", "E3", "A3", "G3", "E3", "F3", "E3", "D3", "C3"]
56
+
57
+ - source: "Davis & Lybbert"
58
+ tonal_center: C
59
+ mode: ionian
60
+ pitches: ["C3", "E3", "D3", "G3", "A3", "G3", "E3", "F3", "D3", "C3"]
61
+
62
+ - source: "Davis & Lybbert"
63
+ tonal_center: C
64
+ mode: ionian
65
+ pitches: ["C3", "D3", "E3", "G3", "A3", "F3", "E3", "D3", "C3"]
66
+
67
+ - source: "Davis & Lybbert"
68
+ tonal_center: G
69
+ mode: ionian
70
+ pitches: ["G3", "F#3", "G3", "E3", "D3", "B2", "C3", "D3", "B2", "A2", "G2"]
71
+
72
+ - source: "Davis & Lybbert"
73
+ tonal_center: G
74
+ mode: ionian
75
+ pitches: ["G2", "B2", "C3", "D3", "E3", "D3", "B2", "C3", "A2", "G2"]
76
+
77
+ - source: "Davis & Lybbert"
78
+ tonal_center: F
79
+ mode: ionian
80
+ pitches: ["F3", "D3", "C3", "F3", "G3", "A3", "E3", "D3", "G3", "F3"]
81
+
82
+ - source: "Davis & Lybbert"
83
+ tonal_center: A
84
+ mode: aeolian
85
+ pitches: ["A2", "E3", "C3", "D3", "B2", "G2", "A2", "C3", "B2", "A2"]
86
+
87
+ - source: "Davis & Lybbert"
88
+ tonal_center: A
89
+ mode: aeolian
90
+ pitches: ["A2", "B2", "C3", "D3", "E3", "F3", "E3", "C3", "B2", "A2"]
91
+
92
+ - source: "Davis & Lybbert"
93
+ tonal_center: E
94
+ mode: aeolian
95
+ pitches: ["E3", "A3", "B3", "G3", "C4", "A3", "B3", "G3", "F#3", "E3"]
96
+
97
+ - source: "Davis & Lybbert"
98
+ tonal_center: E
99
+ mode: aeolian
100
+ pitches: ["E3", "D3", "C3", "B2", "G2", "A2", "B2", "E3", "G3", "F#3", "E3"]
101
+
102
+ - source: "Davis & Lybbert"
103
+ tonal_center: D
104
+ mode: aeolian
105
+ pitches: ["D3", "F3", "E3", "G3", "F3", "D3", "A3", "G3", "F3", "E3", "D3"]
106
+
107
+ - source: "Schoenberg"
108
+ tonal_center: Eb
109
+ mode: ionian
110
+ pitches: ["Eb", "D", "G3", "Ab3", "C", "Ab3", "F3", "Eb3"]
111
+
112
+ - source: "Schoenberg"
113
+ tonal_center: A
114
+ mode: ionian
115
+ pitches: ["A3", "C#4", "B3", "F#3", "A3", "F#3", "G#3", "A3"]
@@ -0,0 +1,58 @@
1
+ module HeadMusic
2
+ module Content
3
+ module CantusFirmus
4
+ # A pedagogical source of cantus firmus examples.
5
+ # Sources include books and treatises on counterpoint.
6
+ class Source
7
+ SOURCES_DATA = YAML.load_file(File.expand_path("sources.yml", __dir__)).freeze
8
+
9
+ attr_reader :key, :publication_name, :publication_edition, :author_names, :notes
10
+
11
+ class << self
12
+ def all
13
+ @all ||= SOURCES_DATA["cantus_firmus_sources"].map do |key, data|
14
+ new(key: key, data: data)
15
+ end
16
+ end
17
+
18
+ def get(identifier)
19
+ return identifier if identifier.is_a?(self)
20
+
21
+ normalized_key = normalize_key(identifier)
22
+ all.find { |source| source.key == normalized_key }
23
+ end
24
+
25
+ def keys
26
+ all.map(&:key)
27
+ end
28
+
29
+ private
30
+
31
+ # Normalize various source name formats to the YAML key format
32
+ # e.g., "Fux" -> "fux", "Clendinning & Marvin" -> "clendinning_and_marvin"
33
+ def normalize_key(identifier)
34
+ identifier.to_s
35
+ .downcase
36
+ .gsub(/\s*&\s*/, "_and_")
37
+ .gsub(/\s+/, "_")
38
+ .to_sym
39
+ end
40
+ end
41
+
42
+ def initialize(key:, data:)
43
+ @key = key.to_sym
44
+ @publication_name = data["publication_name"]
45
+ @publication_edition = data["publication_edition"]
46
+ @author_names = data["author_names"] || []
47
+ @notes = data["notes"]&.strip
48
+ end
49
+
50
+ def to_s
51
+ publication_name
52
+ end
53
+
54
+ private_class_method :new
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,28 @@
1
+ cantus_firmus_sources:
2
+ fux:
3
+ publication_name: Gradus ad Parnassum
4
+ author_names:
5
+ - Johann Joseph Fux
6
+ notes: >
7
+ A foundational text on counterpoint by Johann Joseph Fux, first published in 1725.
8
+ clendinning_and_marvin:
9
+ publication_name: The Musician's Guide to Theory and Analysis
10
+ publication_edition: 3rd
11
+ author_names:
12
+ - Jane Piper Clendinning
13
+ - Elizabeth West Marvin
14
+ notes: >
15
+ A contemporary guide to music theory and analysis, widely used in music education.
16
+ davis_and_lybbert:
17
+ publication_name: The Essentials of Counterpoint
18
+ author_names:
19
+ - Ferdinand Davis
20
+ - Donald Lybbert
21
+ notes: >
22
+ A comprehensive text on counterpoint techniques and practices.
23
+ schoenberg:
24
+ publication_name: Preliminary Exercises in Counterpoint
25
+ author_names:
26
+ - Arnold Schoenberg
27
+ notes: >
28
+ A set of exercises by Arnold Schoenberg designed to teach the fundamentals of counterpoint.
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "11.2.0"
2
+ VERSION = "11.3.0"
3
3
  end
data/lib/head_music.rb CHANGED
@@ -110,7 +110,8 @@ require "head_music/instruments/score_order"
110
110
 
111
111
  # content
112
112
  require "head_music/content/bar"
113
- require "head_music/content/cantus_firmus_examples"
113
+ require "head_music/content/cantus_firmus/source"
114
+ require "head_music/content/cantus_firmus/example"
114
115
  require "head_music/content/composition"
115
116
  require "head_music/content/note"
116
117
  require "head_music/content/placement"
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: 11.2.0
4
+ version: 11.3.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: 2026-01-29 00:00:00.000000000 Z
11
+ date: 2026-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -181,7 +181,10 @@ files:
181
181
  - lib/head_music/analysis/pitch_collection.rb
182
182
  - lib/head_music/analysis/sonority.rb
183
183
  - lib/head_music/content/bar.rb
184
- - lib/head_music/content/cantus_firmus_examples.rb
184
+ - lib/head_music/content/cantus_firmus/example.rb
185
+ - lib/head_music/content/cantus_firmus/examples.yml
186
+ - lib/head_music/content/cantus_firmus/source.rb
187
+ - lib/head_music/content/cantus_firmus/sources.yml
185
188
  - lib/head_music/content/composition.rb
186
189
  - lib/head_music/content/note.rb
187
190
  - lib/head_music/content/placement.rb
@@ -1,58 +0,0 @@
1
- module HeadMusic
2
- module Content
3
- # Sample cantus firmus examples from various pedagogical sources.
4
- # These are traditional melodies used for teaching counterpoint.
5
- module CantusFirmusExamples
6
- FUX = [
7
- {source: "Fux", tonality: "D dorian", pitches: %w[D F E D G F A G F E D]},
8
- {source: "Fux", tonality: "E phrygian", pitches: %w[E C D C A3 A G E F E]},
9
- {source: "Fux", tonality: "F lydian", pitches: %w[F G A F D E F C5 A F G F]},
10
- {source: "Fux", tonality: "G mixolydian", pitches: %w[G3 C B3 G3 C E D G E C D B3 A3 G3]},
11
- {source: "Fux", tonality: "A aeolian", pitches: %w[A3 C B3 D C E F E D C B3 A3]},
12
- {source: "Fux", tonality: "C ionian", pitches: %w[C E F G E A G E F E D C]},
13
- {source: "Fux", tonality: "C ionian", pitches: %w[C E F E G F E D C]}
14
- ].freeze
15
-
16
- CLENDINNING = [
17
- {source: "Clendinning", name: "F major", tonality: "F major", pitches: %w[F3 G3 A3 F3 D3 E3 F3 C4 A3 F3 G3 F3]},
18
- {source: "Clendinning", name: "D minor", tonality: "D minor", pitches: %w[D3 A3 G3 F3 E3 D3 F3 E3 D3]},
19
- {source: "Clendinning", name: "C major (treble)", tonality: "C major", pitches: %w[C D F E F G A G E D C]},
20
- {source: "Clendinning", name: "C major (bass)", tonality: "C major", pitches: %w[C3 E3 F3 G3 E3 A3 G3 E3 F3 E3 D3 C3]}
21
- ].freeze
22
-
23
- DAVIS_AND_LYBBERT = [
24
- {source: "Davis & Lybbert", name: "CF 1 in C major", tonality: "C major", pitches: %w[C3 E3 D3 G3 A3 G3 E3 F3 D3 C3]},
25
- {source: "Davis & Lybbert", name: "CF 2 in C major", tonality: "C major", pitches: %w[C3 D3 E3 G3 A3 F3 E3 D3 C3]},
26
- {source: "Davis & Lybbert", name: "CF 3 in G major", tonality: "G major", pitches: %w[G3 F#3 G3 E3 D3 B2 C3 D3 B2 A2 G2]},
27
- {source: "Davis & Lybbert", name: "CF 4 in G major", tonality: "G major", pitches: %w[G2 B2 C3 D3 E3 D3 B2 C3 A2 G2]},
28
- {source: "Davis & Lybbert", name: "CF 5 in F major", tonality: "F major", pitches: %w[F3 D3 C3 F3 G3 A3 E3 D3 G3 F3]},
29
- {source: "Davis & Lybbert", name: "CF 6 in A minor", tonality: "A minor", pitches: %w[A2 E3 C3 D3 B2 G2 A2 C3 B2 A2]},
30
- {source: "Davis & Lybbert", name: "CF 7 in A minor", tonality: "A minor", pitches: %w[A2 B2 C3 D3 E3 F3 E3 C3 B2 A2]},
31
- {source: "Davis & Lybbert", name: "CF 8 in E minor", tonality: "E minor", pitches: %w[E3 A3 B3 G3 C4 A3 B3 G3 F#3 E3]},
32
- {source: "Davis & Lybbert", name: "CF 9 in E minor", tonality: "E minor", pitches: %w[E3 D3 C3 B2 G2 A2 B2 E3 G3 F#3 E3]},
33
- {source: "Davis & Lybbert", name: "CF 10 in D minor", tonality: "D minor", pitches: %w[D3 F3 E3 G3 F3 D3 A3 G3 F3 E3 D3]}
34
- ].freeze
35
-
36
- SCHOENBERG = [
37
- {source: "Schoenberg", tonality: "Eb major", pitches: %w[Eb D G3 Ab3 C Ab3 F3 Eb3]},
38
- {source: "Schoenberg", tonality: "A major", pitches: %w[A3 C#4 B3 F#3 A3 F#3 G#3 A3]}
39
- ].freeze
40
-
41
- EXAMPLES = (FUX + CLENDINNING + DAVIS_AND_LYBBERT + SCHOENBERG).freeze
42
-
43
- class << self
44
- def all
45
- EXAMPLES
46
- end
47
-
48
- def by_source(source)
49
- EXAMPLES.select { |ex| ex[:source] == source }
50
- end
51
-
52
- def sources
53
- EXAMPLES.map { |ex| ex[:source] }.uniq
54
- end
55
- end
56
- end
57
- end
58
- end