head_music 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # An instrument can be assigned to a staff.
3
+ # A musical instrument.
4
+ # An instrument object can be assigned to a staff object.
5
+ # Attributes:
6
+ # name_key: the name of the instrument
7
+ # alias_name_keys: an array of alternative names for the instrument
8
+ # orchestra_section_key: the section of the orchestra (e.g. "strings")
9
+ # family_key: the key for the family of the instrument (e.g. "saxophone")
10
+ # classification_keys: an array of classification_keys
11
+ # transposition: the number of semitones between the written and the sounding pitch (optional, default: 0)
12
+ # default_clefs: the default clef or system of clefs for the instrument
13
+ # - [treble] for instruments that use the treble clef
14
+ # - [treble, bass] for instruments that use the grand staff
15
+ # notation:
16
+ # a hash of default and alternative notation systems,
17
+ # each with a staffs key with an array of hashes
18
+ # including clef and transposition (where applicable)
19
+ # Associations:
20
+ # family: the family of the instrument (e.g. "saxophone")
21
+ # orchestra_section: the section of the orchestra (e.g. "strings")
4
22
  class HeadMusic::Instrument
5
23
  include HeadMusic::Named
6
24
 
7
25
  INSTRUMENTS = YAML.load_file(File.expand_path("data/instruments.yml", __dir__)).freeze
8
26
 
9
27
  def self.get(name)
10
- return get_by_name(name) if get_by_name(name)
11
- return get_by_name(key_for_name(name)) if key_for_name(name)
12
-
13
- new(name)
28
+ result = get_by_name(name) || get_by_name(key_for_name(name)) || get_by_alias(name)
29
+ result || new(name)
14
30
  end
15
31
 
16
32
  def self.all
17
- INSTRUMENTS.map { |key, _data| get(key) }.sort_by(&:name)
33
+ HeadMusic::InstrumentFamily.all
34
+ @all ||=
35
+ INSTRUMENTS.map { |key, _data| get(key) }.sort_by(&:name)
18
36
  end
19
37
 
20
- attr_reader :name_key, :family, :standard_staves, :classifications
38
+ attr_reader(
39
+ :name_key, :alias_name_keys,
40
+ :family_key, :orchestra_section_key,
41
+ :notation, :classification_keys,
42
+ :fundamental_pitch_spelling, :transposition,
43
+ :default_staffs, :default_clefs
44
+ )
21
45
 
22
46
  def ==(other)
23
47
  to_s == other.to_s
@@ -29,6 +53,36 @@ class HeadMusic::Instrument
29
53
  I18n.translate(name_key, scope: [:instruments], locale: locale)
30
54
  end
31
55
 
56
+ def family
57
+ return unless family_key
58
+
59
+ HeadMusic::InstrumentFamily.get(family_key)
60
+ end
61
+
62
+ # Returns true if the instrument sounds at a different pitch than written.
63
+ def transposing?
64
+ transposition != 0
65
+ end
66
+
67
+ # Returns true if the instrument sounds at a different register than written.
68
+ def transposing_at_the_octave?
69
+ transposing? && transposition % 12 == 0
70
+ end
71
+
72
+ def single_staff?
73
+ default_staffs.length == 1
74
+ end
75
+
76
+ def multiple_staffs?
77
+ default_staffs.length > 1
78
+ end
79
+
80
+ def pitched?
81
+ return false if default_clefs.compact.uniq == ["percussion"]
82
+
83
+ default_clefs.any?
84
+ end
85
+
32
86
  private_class_method :new
33
87
 
34
88
  private
@@ -65,11 +119,37 @@ class HeadMusic::Instrument
65
119
  end
66
120
 
67
121
  def initialize_data_from_record(record)
122
+ initialize_family(record)
123
+ inherit_family_attributes(record)
124
+ initialize_names(record)
125
+ initialize_attributes(record)
126
+ end
127
+
128
+ def initialize_family(record)
129
+ @family_key = record["family_key"]
130
+ @family = HeadMusic::InstrumentFamily.get(family_key)
131
+ end
132
+
133
+ def inherit_family_attributes(record)
134
+ return unless family
135
+
136
+ @orchestra_section_key = family.orchestra_section_key
137
+ @classification_keys = family.classification_keys || []
138
+ end
139
+
140
+ def initialize_names(record)
68
141
  @name_key = record["name_key"].to_sym
69
- @family = record["family"]
70
- @standard_staves = record["standard_staves"] || []
71
- @classifications = record["classifications"] || []
72
142
  self.name = I18n.translate(name_key, scope: "instruments", locale: "en", default: inferred_name)
143
+ @alias_name_keys = record["alias_name_keys"] || []
144
+ end
145
+
146
+ def initialize_attributes(record)
147
+ @orchestra_section_key ||= record["orchestra_section_key"]
148
+ @classification_keys = [@classification_keys, record["classification_keys"]].flatten.compact.uniq
149
+ @fundamental_pitch_spelling = record["fundamental_pitch_spelling"]
150
+ @default_staffs = (record.dig("notation", "default", "staffs") || [])
151
+ @default_clefs = @default_staffs.map { |staff| staff["clef"] }
152
+ @transposition = @default_staffs&.first&.[]("transposition") || 0
73
153
  end
74
154
 
75
155
  def inferred_name
@@ -0,0 +1,69 @@
1
+ # An *InstrumentFamily* is a species of instrument
2
+ # that may exist in a variety of keys or other variations.
3
+ # For example, _saxophone_ is an instrument family, while
4
+ # _alto saxophone_ and _baritone saxophone_ are specific instruments.
5
+ class HeadMusic::InstrumentFamily
6
+ include HeadMusic::Named
7
+
8
+ INSTRUMENT_FAMILIES =
9
+ YAML.load_file(File.expand_path("data/instrument_families.yml", __dir__)).freeze
10
+
11
+ attr_reader :name_key, :classification_keys, :orchestra_section_key, :default_staffs
12
+ attr_accessor :name
13
+
14
+ def self.get(name)
15
+ result = get_by_name(name) || get_by_name(key_for_name(name))
16
+ result || new(name)
17
+ end
18
+
19
+ def self.all
20
+ @all ||=
21
+ INSTRUMENT_FAMILIES.map { |key, _data| get(key) }.sort_by(&:name)
22
+ end
23
+
24
+ private_class_method :new
25
+
26
+ private
27
+
28
+ def initialize(name)
29
+ record = record_for_name(name)
30
+ if record
31
+ initialize_data_from_record(record)
32
+ else
33
+ self.name = name.to_s
34
+ end
35
+ end
36
+
37
+ def record_for_name(name)
38
+ record_for_key(HeadMusic::Utilities::HashKey.for(name)) ||
39
+ record_for_key(key_for_name(name))
40
+ end
41
+
42
+ def key_for_name(name)
43
+ INSTRUMENT_FAMILIES.each do |key, _data|
44
+ I18n.config.available_locales.each do |locale|
45
+ translation = I18n.t("instruments.#{key}", locale: locale)
46
+ return key if translation.downcase == name.downcase
47
+ end
48
+ end
49
+ nil
50
+ end
51
+
52
+ def record_for_key(key)
53
+ INSTRUMENT_FAMILIES.each do |name_key, data|
54
+ return data.merge!("name_key" => name_key) if name_key.to_s == key.to_s
55
+ end
56
+ nil
57
+ end
58
+
59
+ def initialize_data_from_record(record)
60
+ @name_key = record["name_key"].to_sym
61
+ @orchestra_section_key = record["orchestra_section_key"]
62
+ @classification_keys = record["classification_keys"] || []
63
+ self.name = I18n.translate(name_key, scope: "instruments", locale: "en", default: inferred_name)
64
+ end
65
+
66
+ def inferred_name
67
+ name_key.to_s.tr("_", " ")
68
+ end
69
+ end
@@ -6,7 +6,7 @@ de:
6
6
  baritone_f_clef: Baritonschlüssel
7
7
  bass_clef: Bassschlüssel
8
8
  c_clef: C-Schlüssel
9
- choral_tenor_clef: oktavierten Violinschlüssel
9
+ vocal_tenor_clef: oktavierten Violinschlüssel
10
10
  contrabass_clef: contrabass clef
11
11
  countertenor_clef: countertenor clef
12
12
  double_treble_clef: double treble clef
@@ -61,10 +61,10 @@ de:
61
61
  bagpipe: Dudelsack
62
62
  baritone_horn: Baritonhorn
63
63
  baritone_voice: Bariton
64
- bass_voice: Bass
65
64
  bass_clarinet: Bassklarinette
66
65
  bass_drum: Grosse Trommel
67
66
  bass_trombone: Bass Posaune
67
+ bass_voice: Bass
68
68
  bassoon: Fagott
69
69
  castanets: Kastagnetten
70
70
  celesta: Celesta
@@ -78,7 +78,7 @@ de:
78
78
  english_horn: Englischhorn
79
79
  euphonium: Euphonium
80
80
  fiddle: Geige
81
- flugel_horn: Flügelhorn
81
+ flugelhorn: Flügelhorn
82
82
  flute: Flöte
83
83
  french_horn: Horn
84
84
  glockenspiel: Glockenspiel
@@ -6,7 +6,7 @@ en:
6
6
  baritone_f_clef: baritone F-clef
7
7
  bass_clef: bass clef
8
8
  c_clef: C-clef
9
- choral_tenor_clef: choral tenor clef
9
+ vocal_tenor_clef: vocal tenor clef
10
10
  contrabass_clef: contrabass clef
11
11
  countertenor_clef: countertenor clef
12
12
  double_treble_clef: double treble clef
@@ -92,7 +92,6 @@ en:
92
92
  bass_drum: bass drum
93
93
  bass_guitar: bass guitar
94
94
  bass_oboe: bass oboe
95
- bass_drum: bass drum
96
95
  bass_trombone: bass trombone
97
96
  bass_tuba: bass tuba
98
97
  bass_voice: bass
@@ -102,21 +101,20 @@ en:
102
101
  cello: cello
103
102
  chimes: chimes
104
103
  cimbalom: cimbalom
105
- cowbell: cowbell
106
104
  clarinet: clarinet
107
- clavichord: clavichord
108
105
  clash_cymbals: clash cymbals
109
- cornet: cornet
110
- contrabassoon: contrabassoon
106
+ clavichord: clavichord
111
107
  contra_bass_clarinet: contra bass clarinet
108
+ contrabassoon: contrabassoon
112
109
  cor_anglais: cor anglais
110
+ cornet: cornet
113
111
  cowbell: cowbell
114
- cymbal: cymbal
115
112
  crotales: crotales
113
+ cymbal: cymbal
116
114
  double_bass: double bass
117
115
  english_horn: English horn
118
116
  euphonium: euphonium
119
- flugel_horn: flugel horn
117
+ flugelhorn: flugelhorn
120
118
  flute: flute
121
119
  french_horn: French horn
122
120
  glockenspiel: glockenspiel
@@ -6,7 +6,7 @@ es:
6
6
  baritone_f_clef: clave de fa en tercera
7
7
  bass_clef: clave de bajo
8
8
  c_clef: clave de do
9
- choral_tenor_clef: choral tenor clef
9
+ vocal_tenor_clef: vocal tenor clef
10
10
  contrabass_clef: clave de contrabajo
11
11
  countertenor_clef: clave de contratenor
12
12
  double_treble_clef: double clave de sol
@@ -72,7 +72,7 @@ es:
72
72
  double_bass: contrabajo
73
73
  english_horn: Cuerno inglés
74
74
  euphonium: euphonium
75
- flugel_horn: fliscorno
75
+ flugelhorn: fliscorno
76
76
  flute: flauta
77
77
  french_horn: corno
78
78
  glockenspiel: campanólogo
@@ -6,7 +6,7 @@ fr:
6
6
  baritone_f_clef: clé de fa 3e
7
7
  bass_clef: clé de basse
8
8
  c_clef: clé d'ut
9
- choral_tenor_clef: choral tenor clef
9
+ vocal_tenor_clef: vocal tenor clef
10
10
  contrabass_clef: clé de contrebasse
11
11
  countertenor_clef: clé de contre-ténor
12
12
  double_treble_clef: double clé de sol
@@ -6,7 +6,7 @@ it:
6
6
  baritone_f_clef: chiave di baritono
7
7
  bass_clef: chiave di basso
8
8
  c_clef: chiave di do
9
- choral_tenor_clef: chiave di sol in ottava
9
+ vocal_tenor_clef: chiave di sol in ottava
10
10
  contrabass_clef: chiave di contrabbasso
11
11
  countertenor_clef: chiave di controtenore
12
12
  double_treble_clef: chiave di sol in ottava
@@ -76,7 +76,7 @@ it:
76
76
  double_bass: contrabasso
77
77
  english_horn: corno inglese
78
78
  euphonium: eufonio
79
- flugel_horn: flicorno
79
+ flugelhorn: flicorno
80
80
  flute: flauto
81
81
  french_horn: corno
82
82
  glockenspiel: glockenspiel
@@ -91,9 +91,9 @@ it:
91
91
  natural_horn: corno naturale
92
92
  oboe: oboe
93
93
  oboe_d_amore: oboe d'amore
94
- piccolo: ottavino
95
94
  organ: organo
96
95
  piano: piano
96
+ piccolo: ottavino
97
97
  recorder: flauto dolce
98
98
  saxophone: sassofono
99
99
  snare_drum: tamburo rullante
@@ -32,7 +32,7 @@ ru:
32
32
  double_bass: kontrabas
33
33
  english_horn: angliiskii rozhok
34
34
  euphonium: evfonium
35
- flugel_horn: fliugel'gorn
35
+ flugelhorn: fliugel'gorn
36
36
  flute: fleita
37
37
  french_horn: Gorn
38
38
  glockenspiel: kolokol'chiki
@@ -67,4 +67,4 @@ ru:
67
67
  violoncello: violonchel'
68
68
  voice: golos
69
69
  xylophone: ksilofon
70
- zither: tsitra
70
+ zither: tsitra
@@ -5,11 +5,14 @@ class HeadMusic::Staff
5
5
  DEFAULT_LINE_COUNT = 5
6
6
 
7
7
  attr_reader :default_clef, :line_count, :instrument
8
- alias_method :clef, :default_clef
9
8
 
10
9
  def initialize(default_clef, instrument: nil, line_count: nil)
11
10
  @default_clef = HeadMusic::Clef.get(default_clef)
12
11
  @line_count = line_count || DEFAULT_LINE_COUNT
13
12
  @instrument = HeadMusic::Instrument.get(instrument) if instrument
14
13
  end
14
+
15
+ def clef
16
+ default_clef || instrument&.default_staffs&.first
17
+ end
15
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HeadMusic
4
- VERSION = "1.0.0"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/head_music.rb CHANGED
@@ -39,8 +39,8 @@ require "head_music/circle"
39
39
  require "head_music/clef"
40
40
  require "head_music/consonance"
41
41
  require "head_music/diatonic_interval"
42
- require "head_music/grand_staff"
43
42
  require "head_music/harmonic_interval"
43
+ require "head_music/instrument_family"
44
44
  require "head_music/instrument"
45
45
  require "head_music/interval_cycle"
46
46
  require "head_music/key_signature"
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: 1.0.0
4
+ version: 2.0.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: 2023-05-03 00:00:00.000000000 Z
11
+ date: 2023-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -132,11 +132,12 @@ files:
132
132
  - lib/head_music/content/rhythmic_value.rb
133
133
  - lib/head_music/content/voice.rb
134
134
  - lib/head_music/data/clefs.yml
135
+ - lib/head_music/data/instrument_families.yml
135
136
  - lib/head_music/data/instruments.yml
136
137
  - lib/head_music/diatonic_interval.rb
137
- - lib/head_music/grand_staff.rb
138
138
  - lib/head_music/harmonic_interval.rb
139
139
  - lib/head_music/instrument.rb
140
+ - lib/head_music/instrument_family.rb
140
141
  - lib/head_music/interval_cycle.rb
141
142
  - lib/head_music/key_signature.rb
142
143
  - lib/head_music/letter_name.rb
@@ -234,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
235
  - !ruby/object:Gem::Version
235
236
  version: '0'
236
237
  requirements: []
237
- rubygems_version: 3.3.26
238
+ rubygems_version: 3.4.12
238
239
  signing_key:
239
240
  specification_version: 4
240
241
  summary: The rudiments of western music theory.
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # A grand staff is a group of staves for a single instrument, such as a piano.
4
- class HeadMusic::GrandStaff
5
- GRAND_STAVES = {
6
- piano: {
7
- instrument: :piano,
8
- staves: [
9
- {clef: :treble_clef, for: :right_hand},
10
- {clef: :bass_clef, for: :left_hand}
11
- ]
12
- },
13
- organ: {
14
- instrument: :organ,
15
- staves: [
16
- {clef: :treble_clef, for: :right_hand},
17
- {clef: :bass_clef, for: :left_hand},
18
- {clef: :bass_clef, for: :pedals}
19
- ]
20
- }
21
- }.freeze
22
-
23
- def self.get(name)
24
- @grand_staves ||= {}
25
- hash_key = HeadMusic::Utilities::HashKey.for(name)
26
- return nil unless GRAND_STAVES.key?(hash_key)
27
-
28
- @grand_staves[hash_key] ||= new(hash_key)
29
- end
30
-
31
- attr_reader :identifier, :data
32
-
33
- def initialize(name)
34
- @identifier = HeadMusic::Utilities::HashKey.for(name)
35
- @data = GRAND_STAVES[identifier]
36
- end
37
-
38
- def instrument
39
- @instrument ||= HeadMusic::Instrument.get(data[:instrument])
40
- end
41
-
42
- def staves
43
- @staves ||=
44
- data[:staves].map do |staff|
45
- HeadMusic::Staff.new(staff[:clef], instrument: instrument)
46
- end
47
- end
48
-
49
- def brace_staves_index_first
50
- 0
51
- end
52
-
53
- def brace_staves_index_last
54
- 1
55
- end
56
- end