musicality 0.3.0 → 0.5.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.
- checksums.yaml +4 -4
- data/ChangeLog.md +8 -1
- data/bin/midify +3 -4
- data/examples/composition/auto_counterpoint.rb +53 -0
- data/examples/composition/part_generator.rb +51 -0
- data/examples/composition/scale_exercise.rb +41 -0
- data/examples/{hip.rb → notation/hip.rb} +1 -1
- data/examples/{missed_connection.rb → notation/missed_connection.rb} +1 -1
- data/examples/{song1.rb → notation/song1.rb} +1 -1
- data/examples/{song2.rb → notation/song2.rb} +1 -1
- data/lib/musicality.rb +34 -4
- data/lib/musicality/composition/generation/counterpoint_generator.rb +153 -0
- data/lib/musicality/composition/generation/random_rhythm_generator.rb +39 -0
- data/lib/musicality/composition/model/pitch_class.rb +33 -0
- data/lib/musicality/composition/model/pitch_classes.rb +22 -0
- data/lib/musicality/composition/model/scale.rb +34 -0
- data/lib/musicality/composition/model/scale_class.rb +37 -0
- data/lib/musicality/composition/model/scale_classes.rb +91 -0
- data/lib/musicality/composition/note_generation.rb +31 -0
- data/lib/musicality/composition/transposition.rb +8 -0
- data/lib/musicality/composition/util/adding_sequence.rb +24 -0
- data/lib/musicality/composition/util/biinfinite_sequence.rb +130 -0
- data/lib/musicality/composition/util/compound_sequence.rb +44 -0
- data/lib/musicality/composition/util/probabilities.rb +20 -0
- data/lib/musicality/composition/util/random_sampler.rb +26 -0
- data/lib/musicality/composition/util/repeating_sequence.rb +24 -0
- data/lib/musicality/errors.rb +2 -0
- data/lib/musicality/notation/conversion/score_conversion.rb +1 -1
- data/lib/musicality/notation/conversion/score_converter.rb +3 -3
- data/lib/musicality/notation/model/link.rb +26 -24
- data/lib/musicality/notation/model/links.rb +11 -0
- data/lib/musicality/notation/model/note.rb +14 -15
- data/lib/musicality/notation/model/part.rb +3 -3
- data/lib/musicality/notation/model/pitch.rb +8 -0
- data/lib/musicality/notation/model/score.rb +70 -44
- data/lib/musicality/notation/model/symbols.rb +22 -0
- data/lib/musicality/notation/packing/score_packing.rb +2 -3
- data/lib/musicality/notation/parsing/articulation_parsing.rb +4 -4
- data/lib/musicality/notation/parsing/articulation_parsing.treetop +2 -2
- data/lib/musicality/notation/parsing/link_nodes.rb +2 -14
- data/lib/musicality/notation/parsing/link_parsing.rb +9 -107
- data/lib/musicality/notation/parsing/link_parsing.treetop +4 -12
- data/lib/musicality/notation/parsing/note_node.rb +23 -21
- data/lib/musicality/notation/parsing/note_parsing.rb +70 -70
- data/lib/musicality/notation/parsing/note_parsing.treetop +6 -3
- data/lib/musicality/notation/parsing/pitch_node.rb +4 -2
- data/lib/musicality/performance/conversion/score_collator.rb +3 -3
- data/lib/musicality/performance/midi/midi_util.rb +13 -6
- data/lib/musicality/performance/midi/score_sequencing.rb +17 -0
- data/lib/musicality/printing/lilypond/errors.rb +5 -0
- data/lib/musicality/printing/lilypond/meter_engraving.rb +11 -0
- data/lib/musicality/printing/lilypond/note_engraving.rb +53 -0
- data/lib/musicality/printing/lilypond/part_engraver.rb +12 -0
- data/lib/musicality/printing/lilypond/pitch_engraving.rb +30 -0
- data/lib/musicality/printing/lilypond/score_engraver.rb +78 -0
- data/lib/musicality/version.rb +1 -1
- data/spec/composition/generation/random_rhythm_generator_spec.rb +50 -0
- data/spec/composition/model/pitch_class_spec.rb +75 -0
- data/spec/composition/model/pitch_classes_spec.rb +24 -0
- data/spec/composition/model/scale_class_spec.rb +98 -0
- data/spec/composition/model/scale_spec.rb +110 -0
- data/spec/composition/note_generation_spec.rb +113 -0
- data/spec/composition/transposition_spec.rb +17 -0
- data/spec/composition/util/adding_sequence_spec.rb +176 -0
- data/spec/composition/util/compound_sequence_spec.rb +50 -0
- data/spec/composition/util/probabilities_spec.rb +39 -0
- data/spec/composition/util/random_sampler_spec.rb +47 -0
- data/spec/composition/util/repeating_sequence_spec.rb +151 -0
- data/spec/notation/conversion/score_conversion_spec.rb +3 -3
- data/spec/notation/conversion/score_converter_spec.rb +24 -24
- data/spec/notation/model/link_spec.rb +27 -25
- data/spec/notation/model/note_spec.rb +9 -6
- data/spec/notation/model/pitch_spec.rb +24 -1
- data/spec/notation/model/score_spec.rb +57 -16
- data/spec/notation/packing/score_packing_spec.rb +134 -206
- data/spec/notation/parsing/articulation_parsing_spec.rb +1 -8
- data/spec/notation/parsing/convenience_methods_spec.rb +1 -1
- data/spec/notation/parsing/link_nodes_spec.rb +3 -4
- data/spec/notation/parsing/link_parsing_spec.rb +10 -4
- data/spec/notation/parsing/note_node_spec.rb +8 -7
- data/spec/notation/parsing/note_parsing_spec.rb +9 -12
- data/spec/performance/conversion/score_collator_spec.rb +14 -14
- data/spec/performance/midi/midi_util_spec.rb +26 -0
- data/spec/performance/midi/score_sequencer_spec.rb +1 -1
- metadata +57 -12
- data/lib/musicality/notation/model/program.rb +0 -53
- data/lib/musicality/notation/packing/program_packing.rb +0 -16
- data/spec/notation/model/program_spec.rb +0 -50
- data/spec/notation/packing/program_packing_spec.rb +0 -33
@@ -0,0 +1,39 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class RandomRhythmGenerator
|
4
|
+
attr_reader :durations, :probabilities
|
5
|
+
|
6
|
+
def initialize palette_with_probs
|
7
|
+
@durations, @probabilities = palette_with_probs.entries.transpose
|
8
|
+
@dur_sampler = RandomSampler.new(@durations,@probabilities)
|
9
|
+
end
|
10
|
+
|
11
|
+
def random_dur
|
12
|
+
@dur_sampler.sample
|
13
|
+
end
|
14
|
+
|
15
|
+
def random_rhythm target_dur, end_retries = 5
|
16
|
+
rhythm = []
|
17
|
+
total_dur = 0
|
18
|
+
retries = 0
|
19
|
+
|
20
|
+
while(total_dur < target_dur && retries < end_retries)
|
21
|
+
dur = random_dur
|
22
|
+
|
23
|
+
if (dur + total_dur) <= target_dur
|
24
|
+
total_dur += dur
|
25
|
+
rhythm.push(dur)
|
26
|
+
else
|
27
|
+
retries += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if total_dur < target_dur
|
32
|
+
rhythm.push(target_dur - total_dur)
|
33
|
+
end
|
34
|
+
|
35
|
+
return rhythm
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class PitchClass
|
4
|
+
MOD = Pitch::SEMITONES_PER_OCTAVE
|
5
|
+
|
6
|
+
def self.from_i i
|
7
|
+
i % MOD
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.invert val
|
11
|
+
(MOD - val.to_pc).to_pc
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Pitch
|
16
|
+
def to_pc
|
17
|
+
PitchClass.from_i semitone
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class Fixnum
|
24
|
+
def to_pc
|
25
|
+
PitchClass.from_i self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Enumerable
|
30
|
+
def to_pcs
|
31
|
+
map {|value| value.to_pc }
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
module PitchClasses
|
4
|
+
C = 0
|
5
|
+
Db = 1
|
6
|
+
D = 2
|
7
|
+
Eb = 3
|
8
|
+
E = 4
|
9
|
+
F = 5
|
10
|
+
Gb = 6
|
11
|
+
G = 7
|
12
|
+
Ab = 8
|
13
|
+
A = 9
|
14
|
+
Bb = 10
|
15
|
+
B = 11
|
16
|
+
end
|
17
|
+
|
18
|
+
PITCH_CLASSES = PitchClasses.constants.map do |sym|
|
19
|
+
PitchClasses.const_get(sym)
|
20
|
+
end.sort
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class Scale
|
4
|
+
attr_reader :pitch_class
|
5
|
+
def initialize pitch_class, intervals
|
6
|
+
@pitch_class = pitch_class
|
7
|
+
@intervals = intervals
|
8
|
+
end
|
9
|
+
|
10
|
+
def intervals; @intervals.entries; end
|
11
|
+
|
12
|
+
def size
|
13
|
+
@intervals.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def transpose diff
|
17
|
+
new_pc = (@pitch_class + diff).to_pc
|
18
|
+
Scale.new(new_pc,@intervals)
|
19
|
+
end
|
20
|
+
|
21
|
+
def rotate n
|
22
|
+
diff = AddingSequence.new(@intervals).at(n)
|
23
|
+
new_pc = (@pitch_class + diff).to_pc
|
24
|
+
new_intervals = @intervals.rotate(n)
|
25
|
+
Scale.new(new_pc,new_intervals)
|
26
|
+
end
|
27
|
+
|
28
|
+
def at_octave octave
|
29
|
+
start_pitch = Pitch.new(octave: octave, semitone: @pitch_class)
|
30
|
+
AddingSequence.new(@intervals, start_pitch)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class ScaleClass
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize intervals
|
7
|
+
if intervals.detect {|x| x <= 0 }
|
8
|
+
raise NonPositiveError, "One or more scale intervals (#{intervals}) is non-positive"
|
9
|
+
end
|
10
|
+
@intervals = intervals
|
11
|
+
end
|
12
|
+
|
13
|
+
def intervals; self.entries; end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
self.entries == other.entries
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
return @intervals.each unless block_given?
|
21
|
+
@intervals.each {|x| yield x }
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_pitch_seq start_pitch
|
25
|
+
AddingSequence.new(@intervals, start_pitch)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_scale pitch_class
|
29
|
+
Scale.new(pitch_class, @intervals)
|
30
|
+
end
|
31
|
+
|
32
|
+
def rotate n = 1
|
33
|
+
ScaleClass.new(@intervals.rotate(n))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
module ScaleClasses
|
4
|
+
CHROMATIC = ScaleClass.new([1,1,1,1,1,1,1,1,1,1,1,1])
|
5
|
+
|
6
|
+
module Pentatonic
|
7
|
+
MINOR = ScaleClass.new([3,2,2,3,2])
|
8
|
+
MAJOR = MINOR.rotate(1)
|
9
|
+
EGYPTIAN = MINOR.rotate(2)
|
10
|
+
MINOR_BLUES = MINOR.rotate(3)
|
11
|
+
MAJOR_BLUES = MINOR.rotate(4)
|
12
|
+
|
13
|
+
MODES = {
|
14
|
+
1 => MINOR,
|
15
|
+
2 => MAJOR,
|
16
|
+
3 => EGYPTIAN,
|
17
|
+
4 => MINOR_BLUES,
|
18
|
+
5 => MAJOR_BLUES
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
module Hexatonic
|
23
|
+
WHOLE_TONE = ScaleClass.new([2,2,2,2,2,2])
|
24
|
+
AUGMENTED = ScaleClass.new([3,1,3,1,3,1])
|
25
|
+
MYSTIC = PROMETHEAN = ScaleClass.new([2,2,2,3,1,2])
|
26
|
+
BLUES = ScaleClass.new([3,2,1,1,3,2])
|
27
|
+
TRITONE = PETRUSHKA = ScaleClass.new([1,3,2,1,3,2])
|
28
|
+
end
|
29
|
+
|
30
|
+
module Heptatonic
|
31
|
+
# This is where the standard Major scale and its modes are found, among others.
|
32
|
+
module Prima
|
33
|
+
IONIAN = MAJOR = ScaleClass.new([2,2,1,2,2,2,1])
|
34
|
+
DORIAN = IONIAN.rotate(1)
|
35
|
+
PHRYGIAN = IONIAN.rotate(2)
|
36
|
+
LYDIAN = IONIAN.rotate(3)
|
37
|
+
MIXOLYDIAN = IONIAN.rotate(4)
|
38
|
+
AEOLIAN = MINOR = IONIAN.rotate(5)
|
39
|
+
LOCRIAN = IONIAN.rotate(6)
|
40
|
+
|
41
|
+
MODES = {
|
42
|
+
1 => IONIAN,
|
43
|
+
2 => DORIAN,
|
44
|
+
3 => PHRYGIAN,
|
45
|
+
4 => LYDIAN,
|
46
|
+
5 => MIXOLYDIAN,
|
47
|
+
6 => AEOLIAN,
|
48
|
+
7 => LOCRIAN
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
module Secunda
|
53
|
+
JAZZ_MINOR = MELODIC_MINOR = ScaleClass.new([2,1,2,2,2,2,1])
|
54
|
+
PHRYGIAN_RAISED_SIXTH = MELODIC_MINOR.rotate(1)
|
55
|
+
LYDIAN_RAISED_FIFTH = MELODIC_MINOR.rotate(2)
|
56
|
+
ACOUSTIC = LYDIAN_DOMINANT = MELODIC_MINOR.rotate(3)
|
57
|
+
MAJOR_MINOR = MELODIC_MINOR.rotate(4)
|
58
|
+
HALF_DIMINISHED = MELODIC_MINOR.rotate(5)
|
59
|
+
ALTERED = MELODIC_MINOR.rotate(6)
|
60
|
+
|
61
|
+
MODES = {
|
62
|
+
1 => MELODIC_MINOR,
|
63
|
+
2 => PHRYGIAN_RAISED_SIXTH,
|
64
|
+
3 => LYDIAN_RAISED_FIFTH,
|
65
|
+
4 => ACOUSTIC,
|
66
|
+
5 => MAJOR_MINOR,
|
67
|
+
6 => HALF_DIMINISHED,
|
68
|
+
7 => ALTERED
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
module Other
|
73
|
+
GYPSY = ScaleClass.new([1,3,1,2,1,3,1])
|
74
|
+
HUNGARIAN = ScaleClass.new([2,1,3,1,1,3,1])
|
75
|
+
PHRYGIAN_MAJOR = ScaleClass.new([1,3,1,2,1,2,2])
|
76
|
+
SCALA_ENIGMATICA = ScaleClass.new([1,3,2,2,2,1,1])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module Octatonic
|
81
|
+
WHOLE_HALF = ScaleClass.new([2,1,2,1,2,1,2,1])
|
82
|
+
HALF_WHOLE = WHOLE_HALF.rotate(1)
|
83
|
+
|
84
|
+
MODES = {
|
85
|
+
1 => WHOLE_HALF,
|
86
|
+
2 => HALF_WHOLE
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
def make_note dur, pitch_group
|
4
|
+
if dur > 0
|
5
|
+
Musicality::Note.new(dur,pitch_group)
|
6
|
+
else
|
7
|
+
Musicality::Note.new(-dur)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
module_function :make_note
|
11
|
+
|
12
|
+
# Whichever is longer, rhythm or pitch_groups, is iterated over once while
|
13
|
+
# the smaller will cycle as necessary.
|
14
|
+
def make_notes rhythm, pitch_groups
|
15
|
+
m,n = rhythm.size, pitch_groups.size
|
16
|
+
raise EmptyError, "rhythm is empty" if m == 0
|
17
|
+
raise EmptyError, "pitch_groups is empty" if n == 0
|
18
|
+
|
19
|
+
if m > n
|
20
|
+
Array.new(m) do |i|
|
21
|
+
make_note(rhythm[i],pitch_groups[i % n])
|
22
|
+
end
|
23
|
+
else
|
24
|
+
Array.new(n) do |i|
|
25
|
+
make_note(rhythm[i % m],pitch_groups[i])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
module_function :make_notes
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Musicality
|
2
|
+
|
3
|
+
class AddingSequence
|
4
|
+
include BiInfiniteSequence
|
5
|
+
|
6
|
+
attr_reader :start_value
|
7
|
+
def initialize pattern, start_val = 0
|
8
|
+
raise EmptyError if pattern.empty?
|
9
|
+
@pattern = pattern
|
10
|
+
@n = pattern.size
|
11
|
+
@start_value = start_val
|
12
|
+
end
|
13
|
+
|
14
|
+
def pattern_size; @pattern.size; end
|
15
|
+
|
16
|
+
def next_value cur_val, cur_idx
|
17
|
+
cur_val + @pattern[cur_idx % @n]
|
18
|
+
end
|
19
|
+
def prev_value cur_val, cur_idx
|
20
|
+
cur_val - @pattern[(cur_idx-1) % @n]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Enumerable
|
2
|
+
def map_with_index
|
3
|
+
return enum_for(:map_with_index) unless block_given?
|
4
|
+
ary = entries
|
5
|
+
Array.new(ary.size) do |i|
|
6
|
+
yield ary[i], i
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Musicality
|
12
|
+
|
13
|
+
module BiInfiniteSequence
|
14
|
+
def at offset
|
15
|
+
if offset.is_a? Enumerable
|
16
|
+
return enum_for(:at,offset) unless block_given?
|
17
|
+
else
|
18
|
+
return at_one(offset)
|
19
|
+
end
|
20
|
+
|
21
|
+
offset_index_pairs = offset.map_with_index {|x,i| [x,i] }.sort
|
22
|
+
results = Array.new(offset.size)
|
23
|
+
|
24
|
+
past = offset_index_pairs.select {|p| p[0] < 0 }
|
25
|
+
present = offset_index_pairs.select {|p| p[0] == 0 }
|
26
|
+
future = offset_index_pairs.select {|p| p[0] > 0 }
|
27
|
+
|
28
|
+
start_val = start_value
|
29
|
+
|
30
|
+
if past.any?
|
31
|
+
value = start_val
|
32
|
+
j = past.size - 1
|
33
|
+
tgt_offset = past[j][0]
|
34
|
+
0.downto(past.first[0]+1) do |i|
|
35
|
+
value = prev_value(value,i)
|
36
|
+
while (i-1) == tgt_offset
|
37
|
+
results[past[j][1]] = value
|
38
|
+
j -= 1
|
39
|
+
tgt_offset = j >= 0 ? past[j][0] : nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if present.any?
|
45
|
+
present.each do |off,index|
|
46
|
+
results[index] = start_val
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if future.any?
|
51
|
+
value = start_val
|
52
|
+
j = 0
|
53
|
+
tgt_offset = future[j][0]
|
54
|
+
0.upto(future.last[0]-1) do |i|
|
55
|
+
value = next_value(value,i)
|
56
|
+
while (i+1) == tgt_offset
|
57
|
+
results[future[j][1]] = value
|
58
|
+
j += 1
|
59
|
+
tgt_offset = j < future.size ? future[j][0] : nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
results.each {|x| yield x }
|
65
|
+
end
|
66
|
+
|
67
|
+
def take n
|
68
|
+
raise NegativeError if n < 0
|
69
|
+
return enum_for(:take,n) unless block_given?
|
70
|
+
return if n == 0
|
71
|
+
|
72
|
+
value = start_value
|
73
|
+
0.upto(n - 1) do |i|
|
74
|
+
yield value
|
75
|
+
value = next_value(value,i)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def take_back n
|
80
|
+
raise NegativeError if n < 0
|
81
|
+
return enum_for(:take_back,n) unless block_given?
|
82
|
+
return if n == 0
|
83
|
+
|
84
|
+
value = start_value
|
85
|
+
0.downto(1 - n) do |i|
|
86
|
+
yield value = prev_value(value,i)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def over range
|
91
|
+
min, max = range.minmax
|
92
|
+
raise EmptyError, "given range (#{range}) is empty" if min.nil?
|
93
|
+
return enum_for(:over,range) unless block_given?
|
94
|
+
|
95
|
+
if min >= 0 && max >= 0
|
96
|
+
value = at(min)
|
97
|
+
range.each do |i|
|
98
|
+
yield value
|
99
|
+
value = next_value(value,i)
|
100
|
+
end
|
101
|
+
elsif max < 0
|
102
|
+
value = at(max+1)
|
103
|
+
values = range.entries.reverse.map do |i|
|
104
|
+
value = prev_value(value,i+1)
|
105
|
+
end
|
106
|
+
values.reverse_each {|x| yield x }
|
107
|
+
else
|
108
|
+
take_back(-min).reverse_each {|x| yield x }
|
109
|
+
take(max + 1){ |x| yield x }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def at_one offset
|
116
|
+
value = start_value
|
117
|
+
if offset >= 0
|
118
|
+
0.upto(offset-1) do |i|
|
119
|
+
value = next_value(value,i)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
0.downto(offset+1) do |i|
|
123
|
+
value = prev_value(value,i)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
return value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|