stretto 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|