Class: HeadMusic::Content::Composition::SchemaValues
- Inherits:
-
Object
- Object
- HeadMusic::Content::Composition::SchemaValues
- Defined in:
- lib/head_music/content/composition/schema_values.rb
Overview
Validates and coerces raw schema values into domain objects for the HashDeserializer. Every method takes the raw value and the path it came from, returning a validated object or raising ArgumentError with that path context. Stateless: it holds no reference to the source hash, so the deserializer stays responsible for where values come from and this class for what a value is allowed to be.
Instance Method Summary collapse
-
#bar_number(bar_hash, index) ⇒ Object
-
#key_signature(value, path) ⇒ Object
KeySignature.get returns a hollow object (nil tonic_spelling) for garbage rather than nil, so presence of the tonic is the real check.
-
#meter(value, path) ⇒ Object
-
#pitch(value, path) ⇒ Object
private
A value that fails to parse would otherwise silently deserialize as a rest.
-
#placement_sounds(placement_hash, path) ⇒ Object
“sounds” is an array of sound data, empty for a rest.
-
#position(value, path) ⇒ Object
Position silently coerces garbage strings to “0:1:000”, which would mislocate content with no error, so the shape is validated up front.
-
#rhythmic_value(value, path) ⇒ Object
-
#sound(value, path) ⇒ Object
private
-
#unpitched_sound(value, path) ⇒ Object
private
A nil name_key is the generic unpitched sound.
-
#valid_rhythmic_value?(rhythmic_value) ⇒ Boolean
private
RhythmicValue.get returns a hollow object (nil unit) for garbage rather than nil, and a tied tail can be hollow while the head parses, so the whole tie chain is checked.
Instance Method Details
#bar_number(bar_hash, index) ⇒ Object
74 75 76 77 78 79 80 |
# File 'lib/head_music/content/composition/schema_values.rb', line 74 def (, index) number = ["number"] unless number.is_a?(Integer) && number >= 0 raise ArgumentError, "bars[#{index}]: bar number must be an Integer of at least 0, got #{number.inspect}" end number end |
#key_signature(value, path) ⇒ Object
KeySignature.get returns a hollow object (nil tonic_spelling) for garbage rather than nil, so presence of the tonic is the real check.
23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/head_music/content/composition/schema_values.rb', line 23 def key_signature(value, path) return nil if value.nil? key_signature = begin HeadMusic::Rudiment::KeySignature.get(value) rescue nil end unless key_signature&.tonic_spelling raise ArgumentError, "#{path}: unknown key signature #{value.inspect}" end key_signature end |
#meter(value, path) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/head_music/content/composition/schema_values.rb', line 37 def meter(value, path) return nil if value.nil? meter = begin HeadMusic::Rudiment::Meter.get(value) rescue nil end unless meter&.top_number&.positive? && meter.bottom_number.positive? raise ArgumentError, "#{path}: unknown meter #{value.inspect}" end meter end |
#pitch(value, path) ⇒ Object (private)
A value that fails to parse would otherwise silently deserialize as a rest.
97 98 99 100 101 102 |
# File 'lib/head_music/content/composition/schema_values.rb', line 97 def pitch(value, path) pitch = HeadMusic::Rudiment::Pitch.get(value) raise ArgumentError, "#{path}: unknown pitch #{value.inspect}" unless pitch pitch end |
#placement_sounds(placement_hash, path) ⇒ Object
“sounds” is an array of sound data, empty for a rest. A pitched sound is a pitch string; an unpitched sound is a one-key => name_key hash. A nil element is never a rest, so it fails like any other unknown sound.
63 64 65 66 67 68 69 70 71 72 |
# File 'lib/head_music/content/composition/schema_values.rb', line 63 def placement_sounds(placement_hash, path) values = placement_hash["sounds"] unless values.is_a?(Array) raise ArgumentError, "#{path}: sounds must be an Array, got #{values.inspect}" end values.each_with_index.map do |value, index| sound(value, "#{path}.sounds[#{index}]") end end |
#position(value, path) ⇒ Object
Position silently coerces garbage strings to “0:1:000”, which would mislocate content with no error, so the shape is validated up front. Accepts “bar”, “bar:count”, or “bar:count:tick” with non-negative parts.
12 13 14 15 16 17 18 19 |
# File 'lib/head_music/content/composition/schema_values.rb', line 12 def position(value, path) return nil if value.nil? unless value.is_a?(String) && value.match?(/\A\d+(:\d+){0,2}\z/) raise ArgumentError, "#{path}: unknown position #{value.inspect}" end value end |
#rhythmic_value(value, path) ⇒ Object
51 52 53 54 55 56 57 |
# File 'lib/head_music/content/composition/schema_values.rb', line 51 def rhythmic_value(value, path) rhythmic_value = HeadMusic::Rudiment::RhythmicValue.get(value) unless valid_rhythmic_value?(rhythmic_value) raise ArgumentError, "#{path}: unknown rhythmic value #{value.inspect}" end rhythmic_value end |
#sound(value, path) ⇒ Object (private)
104 105 106 107 108 109 |
# File 'lib/head_music/content/composition/schema_values.rb', line 104 def sound(value, path) return pitch(value, path) if value.is_a?(String) return unpitched_sound(value, path) if value.is_a?(Hash) raise ArgumentError, "#{path}: unknown sound #{value.inspect}" end |
#unpitched_sound(value, path) ⇒ Object (private)
A nil name_key is the generic unpitched sound. A pitched instrument is a valid hit surface (a knock on a violin body is unpitched), so any catalog name or alias resolves.
114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/head_music/content/composition/schema_values.rb', line 114 def unpitched_sound(value, path) unless value.keys == ["unpitched"] raise ArgumentError, "#{path}: unknown sound #{value.inspect}" end name = value["unpitched"] valid_name = name.nil? || (name.is_a?(String) && !name.empty?) sound = HeadMusic::Rudiment::UnpitchedSound.get(name) if valid_name raise ArgumentError, "#{path}: unknown instrument #{name.inspect}" unless sound sound end |
#valid_rhythmic_value?(rhythmic_value) ⇒ Boolean (private)
RhythmicValue.get returns a hollow object (nil unit) for garbage rather than nil, and a tied tail can be hollow while the head parses, so the whole tie chain is checked.
87 88 89 90 91 92 93 |
# File 'lib/head_music/content/composition/schema_values.rb', line 87 def valid_rhythmic_value?(rhythmic_value) return false unless rhythmic_value.is_a?(HeadMusic::Rudiment::RhythmicValue) return false unless rhythmic_value.unit tied_value = rhythmic_value.tied_value tied_value.nil? || valid_rhythmic_value?(tied_value) end |