head_music 0.29.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,7 @@ class HeadMusic::HarmonicInterval
7
7
  def initialize(voice1, voice2, position)
8
8
  @voice1 = voice1
9
9
  @voice2 = voice2
10
- @position = position.is_a?(String) ? HeadMusic::Position.new(voice1.composition, position) : position
10
+ @position = position.is_a?(String) ? HeadMusic::Content::Position.new(voice1.composition, position) : position
11
11
  end
12
12
 
13
13
  def diatonic_interval
@@ -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
@@ -82,9 +82,9 @@ class HeadMusic::Meter
82
82
  def beat_unit
83
83
  @beat_unit ||=
84
84
  if compound?
85
- HeadMusic::RhythmicValue.new(HeadMusic::RhythmicUnit.for_denominator_value(bottom_number / 2), dots: 1)
85
+ HeadMusic::Content::RhythmicValue.new(HeadMusic::RhythmicUnit.for_denominator_value(bottom_number / 2), dots: 1)
86
86
  else
87
- HeadMusic::RhythmicValue.new(count_unit)
87
+ HeadMusic::Content::RhythmicValue.new(count_unit)
88
88
  end
89
89
  end
90
90
 
@@ -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
@@ -17,8 +17,8 @@ class HeadMusic::Style::Guidelines::AtLeastEightNotes < HeadMusic::Style::Annota
17
17
 
18
18
  def no_placements_mark
19
19
  HeadMusic::Style::Mark.new(
20
- HeadMusic::Position.new(composition, "1:1"),
21
- HeadMusic::Position.new(composition, "2:1"),
20
+ HeadMusic::Content::Position.new(composition, "1:1"),
21
+ HeadMusic::Content::Position.new(composition, "2:1"),
22
22
  fitness: 0
23
23
  )
24
24
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HeadMusic
4
- VERSION = "0.29.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: 0.29.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