stretto 0.6.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.
- data/CHANGELOG.markdown +0 -0
- data/README.markdown +67 -0
- data/Rakefile +9 -0
- data/lib/stretto.rb +14 -0
- data/lib/stretto/grammar/channel_pressure_grammar.treetop +9 -0
- data/lib/stretto/grammar/chord_grammar.treetop +60 -0
- data/lib/stretto/grammar/controller_change_grammar.treetop +9 -0
- data/lib/stretto/grammar/duration_grammar.treetop +60 -0
- data/lib/stretto/grammar/grammar_helper.rb +6 -0
- data/lib/stretto/grammar/harmonic_chord_grammar.treetop +15 -0
- data/lib/stretto/grammar/harmony_grammar.treetop +15 -0
- data/lib/stretto/grammar/instrument_grammar.treetop +9 -0
- data/lib/stretto/grammar/key_signature_grammar.treetop +10 -0
- data/lib/stretto/grammar/layer_change_grammar.treetop +9 -0
- data/lib/stretto/grammar/measure_grammar.treetop +7 -0
- data/lib/stretto/grammar/note_grammar.treetop +32 -0
- data/lib/stretto/grammar/pitch_bend_grammar.treetop +7 -0
- data/lib/stretto/grammar/polyphonic_pressure_grammar.treetop +9 -0
- data/lib/stretto/grammar/rest_grammar.treetop +9 -0
- data/lib/stretto/grammar/stretto_grammar.treetop +62 -0
- data/lib/stretto/grammar/tempo_grammar.treetop +9 -0
- data/lib/stretto/grammar/timing_grammar.treetop +9 -0
- data/lib/stretto/grammar/tokens/attack_decay_token.rb +42 -0
- data/lib/stretto/grammar/tokens/chord_token.rb +67 -0
- data/lib/stretto/grammar/tokens/controller_change_token.rb +26 -0
- data/lib/stretto/grammar/tokens/duration_token.rb +55 -0
- data/lib/stretto/grammar/tokens/harmonic_chord_token.rb +25 -0
- data/lib/stretto/grammar/tokens/harmony_with_melody_token.rb +66 -0
- data/lib/stretto/grammar/tokens/hash_token.rb +15 -0
- data/lib/stretto/grammar/tokens/key_signature_token.rb +30 -0
- data/lib/stretto/grammar/tokens/measure_token.rb +21 -0
- data/lib/stretto/grammar/tokens/modifier_token.rb +99 -0
- data/lib/stretto/grammar/tokens/note_string_token.rb +106 -0
- data/lib/stretto/grammar/tokens/note_token.rb +25 -0
- data/lib/stretto/grammar/tokens/pattern_token.rb +26 -0
- data/lib/stretto/grammar/tokens/polyphonic_pressure_token.rb +28 -0
- data/lib/stretto/grammar/tokens/rest_token.rb +21 -0
- data/lib/stretto/grammar/tokens/value_token.rb +28 -0
- data/lib/stretto/grammar/tokens/variable_definition_token.rb +30 -0
- data/lib/stretto/grammar/value_grammar.treetop +45 -0
- data/lib/stretto/grammar/variable_grammar.treetop +10 -0
- data/lib/stretto/grammar/voice_change_grammar.treetop +9 -0
- data/lib/stretto/music_elements/channel_pressure.rb +44 -0
- data/lib/stretto/music_elements/chord.rb +194 -0
- data/lib/stretto/music_elements/controller_change.rb +49 -0
- data/lib/stretto/music_elements/harmonic_chord.rb +56 -0
- data/lib/stretto/music_elements/harmony.rb +37 -0
- data/lib/stretto/music_elements/instrument.rb +57 -0
- data/lib/stretto/music_elements/key_signature.rb +110 -0
- data/lib/stretto/music_elements/layer.rb +22 -0
- data/lib/stretto/music_elements/layer_change.rb +39 -0
- data/lib/stretto/music_elements/measure.rb +38 -0
- data/lib/stretto/music_elements/melody.rb +72 -0
- data/lib/stretto/music_elements/modifiers/attack_decay.rb +55 -0
- data/lib/stretto/music_elements/modifiers/chord_intervals.rb +44 -0
- data/lib/stretto/music_elements/modifiers/duration.rb +180 -0
- data/lib/stretto/music_elements/modifiers/value.rb +172 -0
- data/lib/stretto/music_elements/modifiers/variables.rb +365 -0
- data/lib/stretto/music_elements/music_element.rb +88 -0
- data/lib/stretto/music_elements/note.rb +282 -0
- data/lib/stretto/music_elements/pattern.rb +100 -0
- data/lib/stretto/music_elements/pitch_bend.rb +46 -0
- data/lib/stretto/music_elements/polyphonic_pressure.rb +62 -0
- data/lib/stretto/music_elements/rest.rb +26 -0
- data/lib/stretto/music_elements/tempo.rb +42 -0
- data/lib/stretto/music_elements/timing.rb +33 -0
- data/lib/stretto/music_elements/variable.rb +49 -0
- data/lib/stretto/music_elements/voice.rb +49 -0
- data/lib/stretto/music_elements/voice_change.rb +39 -0
- data/lib/stretto/parsers/exceptions.rb +11 -0
- data/lib/stretto/parsers/parser.rb +89 -0
- data/lib/stretto/renderers/midiator/player.rb +200 -0
- data/lib/stretto/util/jfugue_format_parser.rb +20 -0
- data/lib/stretto/util/node.rb +5 -0
- data/lib/stretto/util/utils.rb +41 -0
- data/lib/stretto/version.rb +3 -0
- metadata +203 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Stretto
|
2
|
+
module MusicElements
|
3
|
+
|
4
|
+
# This module encapsulates behavior for all elements that specify attack and decay values
|
5
|
+
#
|
6
|
+
# Attack represents the "warm up" of the note, and decay its "cool down", that is, the
|
7
|
+
# times it takes to the note to reach its main volume form 0 when building, and the
|
8
|
+
# inverse when going into silence. The default value for both is 0
|
9
|
+
module AttackDecay
|
10
|
+
|
11
|
+
DEFAULT_ATTACK = 64
|
12
|
+
DEFAULT_DECAY = 64
|
13
|
+
|
14
|
+
attr_reader :original_attack, :original_decay
|
15
|
+
|
16
|
+
# Sets up the original attack and decay tokens
|
17
|
+
def build_attack_and_decay(original_attack, original_decay)
|
18
|
+
@original_attack = original_attack
|
19
|
+
@original_decay = original_decay
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the instance variable `@attack` and performs validation in range
|
23
|
+
#
|
24
|
+
# @raise [Exceptions::InvalidValueException] if the value is greater than 127
|
25
|
+
def attack=(attack)
|
26
|
+
@attack = attack || DEFAULT_ATTACK
|
27
|
+
if @attack < 0 or @attack > 127
|
28
|
+
raise Exceptions::InvalidValueException.new("Attack should be in the range 0..127")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sets the instance variable `@decay` and performs validation in range
|
33
|
+
#
|
34
|
+
# @raise [Exceptions::InvalidValueException] if the value is greater than 127
|
35
|
+
def decay=(decay)
|
36
|
+
@decay = decay || DEFAULT_DECAY
|
37
|
+
if @decay < 0 or @decay > 127
|
38
|
+
raise Exceptions::InvalidValueException.new("Decay should be in the range 0..127")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Number] Numeric value for attack, or the default one
|
43
|
+
def attack
|
44
|
+
@original_attack.to_i(@pattern) || DEFAULT_ATTACK
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Number] Numeric value for delay, or the default one
|
48
|
+
def decay
|
49
|
+
@original_decay.to_i(@pattern) || DEFAULT_DECAY
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Stretto
|
2
|
+
module MusicElements
|
3
|
+
|
4
|
+
class Chord < MusicElement
|
5
|
+
|
6
|
+
# The literal names of chord intervals.
|
7
|
+
# Values represent the semitones from the base note of the notes that form the chord.
|
8
|
+
CHORD_INTERVALS = {
|
9
|
+
'maj' => [4, 7],
|
10
|
+
'min' => [3, 7],
|
11
|
+
'aug' => [4, 8],
|
12
|
+
'dim' => [3, 6],
|
13
|
+
'dom7' => [4, 7, 10],
|
14
|
+
'maj7' => [4, 7, 11],
|
15
|
+
'min7' => [3, 7, 10],
|
16
|
+
'sus4' => [5, 7],
|
17
|
+
'sus2' => [2, 7],
|
18
|
+
'maj6' => [4, 7, 9],
|
19
|
+
'min6' => [3, 7, 9],
|
20
|
+
'dom9' => [4, 7, 10, 14],
|
21
|
+
'maj9' => [4, 7, 11, 14],
|
22
|
+
'min9' => [3, 7, 10, 14],
|
23
|
+
'dim7' => [3, 6, 9],
|
24
|
+
'add9' => [4, 7, 14],
|
25
|
+
'min11' => [7, 10, 14, 15, 17],
|
26
|
+
'dom11' => [7, 10, 14, 17],
|
27
|
+
'dom13' => [7, 10, 14, 16, 21],
|
28
|
+
'min13' => [7, 10, 14, 15, 21],
|
29
|
+
'maj13' => [7, 11, 14, 16, 21],
|
30
|
+
'dom7<5' => [4, 6, 10],
|
31
|
+
'dom7>5' => [4, 8, 10],
|
32
|
+
'maj7<5' => [4, 6, 11],
|
33
|
+
'maj7>5' => [4, 8, 11],
|
34
|
+
'minmaj7' => [3, 7, 11],
|
35
|
+
'dom7<5<9' => [4, 6, 10, 13],
|
36
|
+
'dom7<5>9' => [4, 6, 10, 15],
|
37
|
+
'dom7>5<9' => [4, 8, 10, 13],
|
38
|
+
'dom7>5>9' => [4, 8, 10, 15]
|
39
|
+
}
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'rational'
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# This module encapsulates behavior for elements that specify a duration.
|
7
|
+
#
|
8
|
+
# Duration can modify notes, chords and rests. It is specified after the note string,
|
9
|
+
# and its relative to the duration of the whole note (see {Tempo}). That means that a
|
10
|
+
# duration of 1.0 represents a whole note, 0.5 a half note, and 1.5 a whole and a half
|
11
|
+
# note, for instance.
|
12
|
+
#
|
13
|
+
# A duration may be indicated by the duration letters, or literally as a numeric
|
14
|
+
# value.
|
15
|
+
#
|
16
|
+
# In the first way, the letter for the duration is indicated, with the following values:
|
17
|
+
#
|
18
|
+
# +whole+:: w
|
19
|
+
# +half+:: h
|
20
|
+
# +quarter+:: q
|
21
|
+
# +eighth+:: i
|
22
|
+
# +sixteenth+:: s
|
23
|
+
# +thity-second+:: t
|
24
|
+
# +sixty-fourth+:: x
|
25
|
+
# +one-twenty-eighth+:: o
|
26
|
+
#
|
27
|
+
# The duration can be concatenated (so a duration of +hq+ qould be three quarters), and
|
28
|
+
# you may append one or more dot modifiers, that add half the value of the note succesively
|
29
|
+
# (see http://en.wikipedia.org/wiki/Dotted_note).
|
30
|
+
#
|
31
|
+
# Additionally, tuplets can be built by appending a tuplet notation, specified by the
|
32
|
+
# syntax +*+_numerator_+:+_denominator_ (see http://en.wikipedia.org/wiki/Tuplet for more
|
33
|
+
# information about tuplets). If omitter, the numerator and denominator will default to
|
34
|
+
# 3 and 2, respectively (most commonly called a triplet).
|
35
|
+
#
|
36
|
+
# The other way to specify a duration is to indicate its numeric value after a slash.
|
37
|
+
# For example, a C note with half duration can be expressed as +C/0.5+
|
38
|
+
# This notation does not accept dots or tuplets.
|
39
|
+
#
|
40
|
+
# The duration of a note can be tied, so its {#tied_duration tied duration} will be
|
41
|
+
# added to the next {#tied_elements tied elements}. To indicate the start or end of a tie,
|
42
|
+
# add a dash after or before the duration (For example, +Cw- C-w- C-h will tie two
|
43
|
+
# whole notes and a half note together). When added to a pattern, one can get the
|
44
|
+
# total duration of a note by calling {tied_duration}. All elements other than notes, chords
|
45
|
+
# or rests are ignored within tied notes (most notably {Measure measures})
|
46
|
+
module Duration
|
47
|
+
|
48
|
+
DURATIONS = { 'w' => Rational(1),
|
49
|
+
'h' => Rational(1, 2),
|
50
|
+
'q' => Rational(1, 4),
|
51
|
+
'i' => Rational(1, 8),
|
52
|
+
's' => Rational(1, 16),
|
53
|
+
't' => Rational(1, 32),
|
54
|
+
'x' => Rational(1, 64),
|
55
|
+
'o' => Rational(1, 128) }
|
56
|
+
|
57
|
+
DEFAULT_DURATION = DURATIONS['q']
|
58
|
+
DEFAULT_TUPLET_NUMERATOR = 3
|
59
|
+
DEFAULT_TUPLET_DENOMINATOR = 2
|
60
|
+
|
61
|
+
attr_reader :original_duration
|
62
|
+
attr_reader :start_tie, :end_tie
|
63
|
+
|
64
|
+
# @param [Tokens::DurationToken, nil] duration_token
|
65
|
+
# @param [Rational, Number] default_duration The default duration if there's no token
|
66
|
+
def build_duration_from_token(duration_token, default_duration = DEFAULT_DURATION)
|
67
|
+
@original_duration_token = duration_token
|
68
|
+
@original_duration = duration_token ? duration_token.text_value : ''
|
69
|
+
@processed_value = Duration.parse_duration(@original_duration_token, default_duration)
|
70
|
+
if @original_duration_token
|
71
|
+
@start_of_tie = @original_duration_token.start_of_tie?
|
72
|
+
@end_of_tie = @original_duration_token.end_of_tie?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Duration value is proportional to a whole note (one whole note = 1.0 duration).
|
77
|
+
#
|
78
|
+
# This method will coerce the duration to a float, but internally the elements can
|
79
|
+
# handle it as a rational number, to perform calculations more accurately.
|
80
|
+
#
|
81
|
+
# @return [Number] The value of the duration
|
82
|
+
def duration
|
83
|
+
case @processed_value
|
84
|
+
when Stretto::Value then @processed_value.to_f(@pattern)
|
85
|
+
else @processed_value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Boolean] If the note starts a tie, indicating the duration should continue
|
90
|
+
# onto the next element
|
91
|
+
def start_of_tie?
|
92
|
+
@start_of_tie.present?
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Boolean] If the note ends a tie, indicating the duration should extend
|
96
|
+
# the duration of the previous element
|
97
|
+
def end_of_tie?
|
98
|
+
@end_of_tie.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an array containing all the elements that are tied to the right
|
102
|
+
#
|
103
|
+
# It can return an array consisiting on just the current element, if it is not tied
|
104
|
+
# or simply it doesn't belong to a pattern
|
105
|
+
#
|
106
|
+
# @return [Array(MusicElements::MusicElement)] Array of tied elements
|
107
|
+
def tied_elements
|
108
|
+
current = self
|
109
|
+
elements = [current]
|
110
|
+
while next_is_tied?(current)
|
111
|
+
current = current.next
|
112
|
+
elements << current unless current.kind_of?(Measure)
|
113
|
+
end
|
114
|
+
elements
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the total duration of the tied elements
|
118
|
+
#
|
119
|
+
# @return [Number]
|
120
|
+
# @see #tied_elements
|
121
|
+
def tied_duration
|
122
|
+
tied_elements.inject(0){|sum, element| sum + element.duration}
|
123
|
+
end
|
124
|
+
|
125
|
+
# Parses duration from a token, or returning the default duration
|
126
|
+
#
|
127
|
+
# @return [Rational, Number]
|
128
|
+
def self.parse_duration(duration_token, default_duration = DEFAULT_DURATION)
|
129
|
+
if !duration_token
|
130
|
+
default_duration
|
131
|
+
elsif duration_token.value
|
132
|
+
duration_token.value
|
133
|
+
else
|
134
|
+
parse_duration_token(duration_token)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# Returns whether this note is start of tie, and the next one has end of tie.
|
141
|
+
# Ignores Measures, so it will return true even when there's one in between
|
142
|
+
#
|
143
|
+
# @return [Boolean]
|
144
|
+
def next_is_tied?(note)
|
145
|
+
note.start_of_tie? &&
|
146
|
+
note.next &&
|
147
|
+
note.next.end_of_tie? &&
|
148
|
+
(note.class == note.next.class || note.next.kind_of?(Measure) || note.kind_of?(Measure))
|
149
|
+
end
|
150
|
+
|
151
|
+
class << self
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
# Parses the duration when it comes as characters + modifiers
|
156
|
+
# (e.g. "wh.", "q*3:5")
|
157
|
+
#
|
158
|
+
# @return [Rational, Number]
|
159
|
+
def parse_duration_token(duration_token)
|
160
|
+
duration = duration_token.duration_character.split('').map do |char_duration| # TODO: deprecation. each_char is only 1.9+
|
161
|
+
DURATIONS[char_duration.downcase]
|
162
|
+
end.sum
|
163
|
+
original_duration = duration
|
164
|
+
duration_token.dots.times do |dot_duration|
|
165
|
+
duration += (original_duration * Rational(1, 2 ** (dot_duration + 1)))
|
166
|
+
end
|
167
|
+
if duration_token.tuplet
|
168
|
+
numerator = duration_token.tuplet[:numerator] || DEFAULT_TUPLET_NUMERATOR
|
169
|
+
denominator = duration_token.tuplet[:denominator] || DEFAULT_TUPLET_DENOMINATOR
|
170
|
+
duration *= Rational(denominator.to_i, numerator.to_i)
|
171
|
+
end
|
172
|
+
duration
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/variables'
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
|
5
|
+
# This class acts as a placeholder for values contained by musical elements.
|
6
|
+
#
|
7
|
+
# It is needed in order to retrieve values held by a variable until evaluation,
|
8
|
+
# looking for them in the pattern the element belongs, or one of the predefined
|
9
|
+
# variables.
|
10
|
+
class Value
|
11
|
+
|
12
|
+
# Wraps a numeric value
|
13
|
+
# @see Value
|
14
|
+
class NumericValue
|
15
|
+
|
16
|
+
attr_reader :numeric
|
17
|
+
|
18
|
+
# @param [Number] The numeric value held
|
19
|
+
def initialize(numeric)
|
20
|
+
@numeric = numeric
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Integer] The numeric value coerced to an integer
|
24
|
+
def to_i(pattern)
|
25
|
+
@numeric.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Float] The numeric value coerced to a float
|
29
|
+
def to_f(pattern)
|
30
|
+
@numeric.to_f
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Boolean] True if the value it is holding is equal to other's value
|
34
|
+
def ==(other)
|
35
|
+
other.kind_of?(NumericValue) && other.numeric == @numeric
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] The string representation of the numeric value
|
39
|
+
def to_s
|
40
|
+
@numeric.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#--------------------------------------------------------------
|
45
|
+
|
46
|
+
# Wraps a variable value, that is, holds a reference to a value that is going
|
47
|
+
# to be evaluated until requested.
|
48
|
+
#
|
49
|
+
# @see Value
|
50
|
+
class VariableValue
|
51
|
+
|
52
|
+
include Variables
|
53
|
+
|
54
|
+
attr_reader :name
|
55
|
+
|
56
|
+
# @param [String] The name that references the variable
|
57
|
+
def initialize(name)
|
58
|
+
@name = name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the value of the variable as an integer
|
62
|
+
#
|
63
|
+
# @return [Integer]
|
64
|
+
# @raise (see #get_numeric_value)
|
65
|
+
def to_i(pattern)
|
66
|
+
get_numeric_value(pattern){ |value| value.to_i(pattern) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the value of the variable as a float
|
70
|
+
#
|
71
|
+
# @return [Float]
|
72
|
+
# @raise (see #get_numeric_value)
|
73
|
+
def to_f(pattern)
|
74
|
+
get_numeric_value(pattern){ |value| value.to_f(pattern) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns either the variable value if attached to a pattern, or raises an exception
|
78
|
+
# if there is no pattern attached and the variable is not one of the predefined ones.
|
79
|
+
def value(pattern)
|
80
|
+
if pattern
|
81
|
+
pattern.variable(@name)
|
82
|
+
else
|
83
|
+
predefined = PREDEFINED_VARIABLES[name.upcase]
|
84
|
+
unless predefined
|
85
|
+
raise Stretto::Exceptions::VariableContextException.new(
|
86
|
+
"A pattern is needed to access variable #{@name}")
|
87
|
+
end
|
88
|
+
Value.new(Value::NumericValue.new(predefined))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns whether the other object is the same class and holds the same variable name
|
93
|
+
def ==(other)
|
94
|
+
other.kind_of?(VariableValue) && other.name == @name
|
95
|
+
end
|
96
|
+
|
97
|
+
# Name of the variable, enclosed in brackets
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
def to_s
|
101
|
+
"[#{@name}]"
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Gets the numeric value of the variable.
|
107
|
+
#
|
108
|
+
# It does indirection, that is, if the value holding is a variable itself,
|
109
|
+
# looks up recursively.
|
110
|
+
#
|
111
|
+
# @raise [VariableContextException] if the variable is not one of the predefined values
|
112
|
+
# and there is no pattern attached
|
113
|
+
# @raise [VariableNotDefinedException] if the variable is not defined in the pattern
|
114
|
+
def get_numeric_value(pattern)
|
115
|
+
variable = self
|
116
|
+
variable = yield variable.value(pattern) until variable.kind_of?(Numeric)
|
117
|
+
variable
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
#--------------------------------------------------------------
|
122
|
+
|
123
|
+
# Initializes with the value passed in, and an optional variation.
|
124
|
+
#
|
125
|
+
# @param value [NumericValue, VariableValue]
|
126
|
+
# @param variation [Number] Works as a delayed addition, so you can do things like
|
127
|
+
# `Value.new(VariableValue.new("SOME_VAR")) + 1`
|
128
|
+
# and have it evaluated until requested
|
129
|
+
def initialize(value, variation = nil)
|
130
|
+
@value = value
|
131
|
+
@variation = variation
|
132
|
+
end
|
133
|
+
|
134
|
+
# Converts the value to integer
|
135
|
+
#
|
136
|
+
# @return [Integer, nil]
|
137
|
+
def to_i(pattern)
|
138
|
+
if @value
|
139
|
+
result = @value.to_i(pattern)
|
140
|
+
result += @variation if @variation
|
141
|
+
result
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Converts the value to float
|
146
|
+
#
|
147
|
+
# @return [Float, nil]
|
148
|
+
def to_f(pattern)
|
149
|
+
if @value
|
150
|
+
result = @value.to_f(pattern)
|
151
|
+
result += @variation if @variation
|
152
|
+
result
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Adds a variation to the Value, returning a new one with the sum of variations
|
157
|
+
#
|
158
|
+
# @return [Value]
|
159
|
+
def +(variation)
|
160
|
+
new_variation = [@variation, variation].compact.sum
|
161
|
+
self.class.new(@value, new_variation)
|
162
|
+
end
|
163
|
+
|
164
|
+
# String representation for this value, either a number or the name of the variable.
|
165
|
+
def to_s
|
166
|
+
output = @value.to_s
|
167
|
+
output += "+#{@variation}" if @variation
|
168
|
+
output
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|