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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +8 -1
  3. data/bin/midify +3 -4
  4. data/examples/composition/auto_counterpoint.rb +53 -0
  5. data/examples/composition/part_generator.rb +51 -0
  6. data/examples/composition/scale_exercise.rb +41 -0
  7. data/examples/{hip.rb → notation/hip.rb} +1 -1
  8. data/examples/{missed_connection.rb → notation/missed_connection.rb} +1 -1
  9. data/examples/{song1.rb → notation/song1.rb} +1 -1
  10. data/examples/{song2.rb → notation/song2.rb} +1 -1
  11. data/lib/musicality.rb +34 -4
  12. data/lib/musicality/composition/generation/counterpoint_generator.rb +153 -0
  13. data/lib/musicality/composition/generation/random_rhythm_generator.rb +39 -0
  14. data/lib/musicality/composition/model/pitch_class.rb +33 -0
  15. data/lib/musicality/composition/model/pitch_classes.rb +22 -0
  16. data/lib/musicality/composition/model/scale.rb +34 -0
  17. data/lib/musicality/composition/model/scale_class.rb +37 -0
  18. data/lib/musicality/composition/model/scale_classes.rb +91 -0
  19. data/lib/musicality/composition/note_generation.rb +31 -0
  20. data/lib/musicality/composition/transposition.rb +8 -0
  21. data/lib/musicality/composition/util/adding_sequence.rb +24 -0
  22. data/lib/musicality/composition/util/biinfinite_sequence.rb +130 -0
  23. data/lib/musicality/composition/util/compound_sequence.rb +44 -0
  24. data/lib/musicality/composition/util/probabilities.rb +20 -0
  25. data/lib/musicality/composition/util/random_sampler.rb +26 -0
  26. data/lib/musicality/composition/util/repeating_sequence.rb +24 -0
  27. data/lib/musicality/errors.rb +2 -0
  28. data/lib/musicality/notation/conversion/score_conversion.rb +1 -1
  29. data/lib/musicality/notation/conversion/score_converter.rb +3 -3
  30. data/lib/musicality/notation/model/link.rb +26 -24
  31. data/lib/musicality/notation/model/links.rb +11 -0
  32. data/lib/musicality/notation/model/note.rb +14 -15
  33. data/lib/musicality/notation/model/part.rb +3 -3
  34. data/lib/musicality/notation/model/pitch.rb +8 -0
  35. data/lib/musicality/notation/model/score.rb +70 -44
  36. data/lib/musicality/notation/model/symbols.rb +22 -0
  37. data/lib/musicality/notation/packing/score_packing.rb +2 -3
  38. data/lib/musicality/notation/parsing/articulation_parsing.rb +4 -4
  39. data/lib/musicality/notation/parsing/articulation_parsing.treetop +2 -2
  40. data/lib/musicality/notation/parsing/link_nodes.rb +2 -14
  41. data/lib/musicality/notation/parsing/link_parsing.rb +9 -107
  42. data/lib/musicality/notation/parsing/link_parsing.treetop +4 -12
  43. data/lib/musicality/notation/parsing/note_node.rb +23 -21
  44. data/lib/musicality/notation/parsing/note_parsing.rb +70 -70
  45. data/lib/musicality/notation/parsing/note_parsing.treetop +6 -3
  46. data/lib/musicality/notation/parsing/pitch_node.rb +4 -2
  47. data/lib/musicality/performance/conversion/score_collator.rb +3 -3
  48. data/lib/musicality/performance/midi/midi_util.rb +13 -6
  49. data/lib/musicality/performance/midi/score_sequencing.rb +17 -0
  50. data/lib/musicality/printing/lilypond/errors.rb +5 -0
  51. data/lib/musicality/printing/lilypond/meter_engraving.rb +11 -0
  52. data/lib/musicality/printing/lilypond/note_engraving.rb +53 -0
  53. data/lib/musicality/printing/lilypond/part_engraver.rb +12 -0
  54. data/lib/musicality/printing/lilypond/pitch_engraving.rb +30 -0
  55. data/lib/musicality/printing/lilypond/score_engraver.rb +78 -0
  56. data/lib/musicality/version.rb +1 -1
  57. data/spec/composition/generation/random_rhythm_generator_spec.rb +50 -0
  58. data/spec/composition/model/pitch_class_spec.rb +75 -0
  59. data/spec/composition/model/pitch_classes_spec.rb +24 -0
  60. data/spec/composition/model/scale_class_spec.rb +98 -0
  61. data/spec/composition/model/scale_spec.rb +110 -0
  62. data/spec/composition/note_generation_spec.rb +113 -0
  63. data/spec/composition/transposition_spec.rb +17 -0
  64. data/spec/composition/util/adding_sequence_spec.rb +176 -0
  65. data/spec/composition/util/compound_sequence_spec.rb +50 -0
  66. data/spec/composition/util/probabilities_spec.rb +39 -0
  67. data/spec/composition/util/random_sampler_spec.rb +47 -0
  68. data/spec/composition/util/repeating_sequence_spec.rb +151 -0
  69. data/spec/notation/conversion/score_conversion_spec.rb +3 -3
  70. data/spec/notation/conversion/score_converter_spec.rb +24 -24
  71. data/spec/notation/model/link_spec.rb +27 -25
  72. data/spec/notation/model/note_spec.rb +9 -6
  73. data/spec/notation/model/pitch_spec.rb +24 -1
  74. data/spec/notation/model/score_spec.rb +57 -16
  75. data/spec/notation/packing/score_packing_spec.rb +134 -206
  76. data/spec/notation/parsing/articulation_parsing_spec.rb +1 -8
  77. data/spec/notation/parsing/convenience_methods_spec.rb +1 -1
  78. data/spec/notation/parsing/link_nodes_spec.rb +3 -4
  79. data/spec/notation/parsing/link_parsing_spec.rb +10 -4
  80. data/spec/notation/parsing/note_node_spec.rb +8 -7
  81. data/spec/notation/parsing/note_parsing_spec.rb +9 -12
  82. data/spec/performance/conversion/score_collator_spec.rb +14 -14
  83. data/spec/performance/midi/midi_util_spec.rb +26 -0
  84. data/spec/performance/midi/score_sequencer_spec.rb +1 -1
  85. metadata +57 -12
  86. data/lib/musicality/notation/model/program.rb +0 -53
  87. data/lib/musicality/notation/packing/program_packing.rb +0 -16
  88. data/spec/notation/model/program_spec.rb +0 -50
  89. data/spec/notation/packing/program_packing_spec.rb +0 -33
@@ -0,0 +1,98 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ valid = [ [2,2,1,2,2,2,1], [1]*12 ]
4
+
5
+ describe ScaleClass do
6
+ describe '#initialize' do
7
+ context 'given non-positive intervals' do
8
+ it 'should raise NonPositiveError' do
9
+ [ [3,6,-1,4], [-1,13], [4,4,4,-1,1] ].each do |intervals|
10
+ expect { ScaleClass.new(intervals) }.to raise_error(NonPositiveError)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#intervals' do
17
+ it 'should return intervals given to #initialize' do
18
+ valid.each do |intervals|
19
+ ScaleClass.new(intervals).intervals.should eq(intervals)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#==' do
25
+ it 'should compare given enumerable to intervals' do
26
+ valid.each do |intervals|
27
+ sc = ScaleClass.new(intervals)
28
+ sc.should eq(intervals)
29
+ sc.should_not eq(intervals + [2])
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#rotate' do
35
+ it 'should return a new ScaleClass, with rotated intervals' do
36
+ valid.each do |intervals|
37
+ sc = ScaleClass.new(intervals)
38
+ [ 0, 1, -1, 4, -3, 2, 6 ].each do |n|
39
+ sc2 = sc.rotate(n)
40
+ sc2.should_not be(sc)
41
+ sc2.should eq(intervals.rotate(n))
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'should rotate by 1, by default' do
47
+ intervals = valid.first
48
+ ScaleClass.new(intervals).rotate.should eq(intervals.rotate(1))
49
+ end
50
+ end
51
+
52
+ describe '#each' do
53
+ before :all do
54
+ @sc = ScaleClass.new(valid.first)
55
+ end
56
+
57
+ context 'block given' do
58
+ it 'should yield all interval values' do
59
+ xs = []
60
+ @sc.each do |x|
61
+ xs.push(x)
62
+ end
63
+ xs.should eq(@sc.intervals)
64
+ end
65
+ end
66
+
67
+ context 'no block given' do
68
+ it 'should return an enumerator' do
69
+ @sc.each.should be_a Enumerator
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#to_pitch_seq' do
75
+ before :all do
76
+ @sc = ScaleClass.new([2,2,1,2,2,2,1])
77
+ @start_pitch = C4
78
+ @first_octave = [C4,D4,E4,F4,G4,A4,B4,C5]
79
+ @prev_octave = [C3,D3,E3,F3,G3,A3,B3,C4]
80
+ @pseq = @sc.to_pitch_seq(@start_pitch)
81
+ end
82
+
83
+ it 'should return a AddingSequence' do
84
+ @pseq.should be_a AddingSequence
85
+ end
86
+
87
+ it 'should be centered at given start pitch' do
88
+ @pseq.at(0).should eq(@start_pitch)
89
+ end
90
+
91
+ it 'should walk forward/backward through scale' do
92
+ @pseq.take(8).to_a.should eq(@first_octave)
93
+ @pseq.over(0...8).to_a.should eq(@first_octave)
94
+ @pseq.take_back(7).to_a.should eq(@prev_octave.reverse.drop(1))
95
+ @pseq.over(-7..0).to_a.should eq(@prev_octave)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,110 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ include PitchClasses
4
+
5
+ describe Scale do
6
+ before :all do
7
+ @intervals = [2,2,1,2,2,2,1]
8
+ @pc = C
9
+ @scale = Scale.new(@pc,@intervals)
10
+ end
11
+
12
+ describe '#pitch_class' do
13
+ it 'should return the pitch class given to #initialize' do
14
+ @scale.pitch_class.should eq(@pc)
15
+ end
16
+ end
17
+
18
+ describe '#size' do
19
+ it 'should return the size of the scale intervals' do
20
+ @scale.size.should eq(@intervals.size)
21
+ end
22
+ end
23
+
24
+ describe '#transpose' do
25
+ before :all do
26
+ @diff2 = 3
27
+ @scale2 = @scale.transpose(@diff2)
28
+ @diff3 = -5
29
+ @scale3 = @scale.transpose(@diff3)
30
+ end
31
+
32
+ it 'should return a new Scale' do
33
+ @scale2.should be_a Scale
34
+ @scale2.should_not be @scale
35
+ @scale3.should be_a Scale
36
+ @scale3.should_not be @scale
37
+ end
38
+
39
+ it 'should return a scale with same intervals' do
40
+ @scale2.intervals.should eq @scale.intervals
41
+ @scale3.intervals.should eq @scale.intervals
42
+ end
43
+
44
+ it 'should return a scale with a shifted pitch class' do
45
+ @scale2.pitch_class.should eq((@scale.pitch_class + @diff2).to_pc)
46
+ @scale3.pitch_class.should eq((@scale.pitch_class + @diff3).to_pc)
47
+ end
48
+ end
49
+
50
+ describe '#rotate' do
51
+ before :all do
52
+ @n2 = 5
53
+ @scale2 = @scale.rotate(@n2)
54
+ @n3 = -3
55
+ @scale3 = @scale.rotate(@n3)
56
+ end
57
+
58
+ it 'should return a new Scale' do
59
+ @scale2.should be_a Scale
60
+ @scale2.should_not be @scale
61
+ @scale3.should be_a Scale
62
+ @scale3.should_not be @scale
63
+ end
64
+
65
+ it 'should return a scale with rotated intervals' do
66
+ @scale2.intervals.should eq @scale.intervals.rotate(@n2)
67
+ @scale3.intervals.should eq @scale.intervals.rotate(@n3)
68
+ end
69
+
70
+ it 'should return a scale with a shifted pitch class' do
71
+ pc2 = (AddingSequence.new(@scale.intervals).at(@n2) + @scale.pitch_class).to_pc
72
+ @scale2.pitch_class.should eq(pc2)
73
+ pc3 = (AddingSequence.new(@scale.intervals).at(@n3) + @scale.pitch_class).to_pc
74
+ @scale3.pitch_class.should eq(pc3)
75
+ end
76
+ end
77
+
78
+ describe '#at_octave' do
79
+ before :all do
80
+ @octave = 2
81
+ @pitch_seq = @scale.at_octave(@octave)
82
+ @start_pitch = @pitch_seq.at(0)
83
+ end
84
+
85
+ it 'should return a bi-infinite sequence' do
86
+ @pitch_seq.should be_a BiInfiniteSequence
87
+ end
88
+
89
+ it 'should start sequence at scale pitch class and given octave' do
90
+ @start_pitch.semitone.should eq(@scale.pitch_class)
91
+ @start_pitch.octave.should eq(@octave)
92
+ end
93
+
94
+ it 'should make sequence that proceeds forwards along scale intervals' do
95
+ first_pitches = @pitch_seq.over(0..@scale.intervals.size).to_a
96
+ first_pitches[1..-1].each_with_index do |pitch,i|
97
+ diff = pitch.diff(first_pitches[i])
98
+ diff.should eq(@scale.intervals[i])
99
+ end
100
+ end
101
+
102
+ it 'should make sequence that proceeds backwards along scale intervals' do
103
+ first_pitches = @pitch_seq.over(-@scale.intervals.size..0).to_a
104
+ first_pitches[1..-1].each_with_index do |pitch,i|
105
+ diff = pitch.diff(first_pitches[i])
106
+ diff.should eq(@scale.intervals[i])
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,113 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe 'make_note' do
4
+ it 'should return a Note' do
5
+ make_note(0.3,C3).should be_a Note
6
+ end
7
+
8
+ context 'given single pitch' do
9
+ it 'should have the given duration and pitch' do
10
+ n = make_note(0.3,C3)
11
+ n.duration.should eq(0.3)
12
+ n.pitches.should eq([C3])
13
+ end
14
+ end
15
+
16
+ context 'given pitch array' do
17
+ it 'should have the given duration and pitches' do
18
+ n = make_note(0.2,[C3,E3,Ab3])
19
+ n.duration.should eq(0.2)
20
+ n.pitches.should eq([C3,E3,Ab3])
21
+ end
22
+ end
23
+
24
+ context 'given negative duration' do
25
+ it 'should have duration with same magnitude' do
26
+ make_note(-0.3,C3).duration.should eq(0.3)
27
+ end
28
+
29
+ it 'should make a rest note (no pitches)' do
30
+ make_note(-0.3,[C3,E3]).pitches.should be_empty
31
+ end
32
+ end
33
+ end
34
+
35
+ describe 'make_notes' do
36
+ context 'given empty rhythm or pitch_groups' do
37
+ it 'should raise EmptyError' do
38
+ expect do
39
+ make_notes([],[A3,B3,C3])
40
+ end.to raise_error(EmptyError)
41
+ expect do
42
+ make_notes([2,2],[])
43
+ end.to raise_error(EmptyError)
44
+ end
45
+ end
46
+
47
+ context 'given equal length rhtyhm and pitch_groups' do
48
+ it 'should produce same number of notes as both' do
49
+ make_notes([2,2,5],[A1,B1,C1]).size.should eq(3)
50
+ end
51
+ end
52
+
53
+ context 'given longer rhythm than pitch_groups' do
54
+ before :all do
55
+ @rhythm = [4,3,3,1]
56
+ @pitch_groups = [[C1],[E2,G2]]
57
+ @notes = make_notes(@rhythm,@pitch_groups)
58
+ end
59
+
60
+ it 'should produce same number of notes as rhythm.size' do
61
+ @notes.size.should eq(@rhythm.size)
62
+ end
63
+
64
+ it 'should follow entire rhythm once' do
65
+ @notes.map {|n| n.duration}.should eq(@rhythm)
66
+ end
67
+
68
+ it 'should cycle through pitch groups as necesary' do
69
+ @notes.map {|n| n.pitches}.should eq(@pitch_groups*2)
70
+ end
71
+ end
72
+
73
+ context 'given longer pitch_groups than rhythm' do
74
+ before :all do
75
+ @rhythm = [4,3,1]
76
+ @pitch_groups = [[C1],[E2,G2],[F5,G5,A5],[F4],[Eb4],[G4]]
77
+ @notes = make_notes(@rhythm,@pitch_groups)
78
+ end
79
+
80
+ it 'should produce same number of notes as pitch_groups.size' do
81
+ @notes.size.should eq(@pitch_groups.size)
82
+ end
83
+
84
+ it 'should follow entire pitch_groups once' do
85
+ @notes.map {|n| n.pitches}.should eq(@pitch_groups)
86
+ end
87
+
88
+ it 'should cycle through rhythm as necesary' do
89
+ @notes.map {|n| n.duration}.should eq(@rhythm*2)
90
+ end
91
+ end
92
+
93
+ context 'given same-length pitch_groups and rhythm' do
94
+ before :all do
95
+ @rhythm = [4,3,1]
96
+ @pitch_groups = [[F4],[Eb4],[G4]]
97
+ @notes = make_notes(@rhythm,@pitch_groups)
98
+ end
99
+
100
+ it 'should produce same number of notes as rhythm.size and pitch_groups.size' do
101
+ @notes.size.should eq(@pitch_groups.size)
102
+ @notes.size.should eq(@rhythm.size)
103
+ end
104
+
105
+ it 'should follow entire rhythm once' do
106
+ @notes.map {|n| n.pitches}.should eq(@pitch_groups)
107
+ end
108
+
109
+ it 'should follow entire pitch groups once' do
110
+ @notes.map {|n| n.duration}.should eq(@rhythm)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe 'transpose' do
4
+ it 'should map given notes to new notes, transposing by given diff' do
5
+ notes = "/4A2,C2 /4D2,F2,Gb2 /8 /8E4".to_notes
6
+ semitones = 3
7
+ notes2 = transpose(notes,semitones)
8
+
9
+ notes2.size.should eq(notes.size)
10
+ notes2.each_index do |i|
11
+ notes2[i].pitches.each_with_index do |pitch2,j|
12
+ pitch = notes[i].pitches[j]
13
+ pitch2.diff(pitch).should eq(semitones)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,176 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe AddingSequence do
4
+ describe '#initialize' do
5
+ context 'given an empty pattern' do
6
+ it 'should raise EmptyError' do
7
+ expect do
8
+ AddingSequence.new([])
9
+ end.to raise_error(EmptyError)
10
+ end
11
+ end
12
+ end
13
+
14
+ describe '#pattern_size' do
15
+ it 'should return the pattern size' do
16
+ AddingSequence.new([1,2,3,4,5]).pattern_size.should eq(5)
17
+ end
18
+ end
19
+
20
+ before :all do
21
+ @pattern = [2,-1,5,-4,3]
22
+ @start_value = 13
23
+ @seq = AddingSequence.new(@pattern,@start_value)
24
+ end
25
+
26
+ describe '#at' do
27
+ context 'given single offset' do
28
+ context 'given offset of 0' do
29
+ it 'should return the start value' do
30
+ [ 0, -3, 7].each do |start_val|
31
+ AddingSequence.new(@pattern,start_val).at(0).should eq(start_val)
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'given offset > 0' do
37
+ it 'should keep adding on pattern elements to start_val until the given offset is reached' do
38
+ [1,2,3,5,8,15,45].each do |offset|
39
+ val = @seq.at(offset)
40
+ rep_seq = RepeatingSequence.new(@pattern)
41
+ val2 = rep_seq.take(offset).inject(@start_value,:+)
42
+ val.should eq(val2)
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'given offset < 0' do
48
+ it 'should keep suctracting pattern elements from start_val until the given offset is reached' do
49
+ [-1,-2,-3,-5,-8,-15,-45].each do |offset|
50
+ val = @seq.at(offset)
51
+ rep_seq = RepeatingSequence.new(@pattern)
52
+ val2 = rep_seq.take_back(-offset).inject(@start_value,:-)
53
+ val.should eq(val2)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'given array of offsets' do
60
+ context 'not given block' do
61
+ it 'should return enumerator' do
62
+ @seq.at([1,2,3]).should be_a Enumerator
63
+ end
64
+ end
65
+
66
+ context 'given block' do
67
+ it 'should yield sequence value for each offset' do
68
+ [ (0..@seq.pattern_size).to_a, (-@seq.pattern_size..0).to_a,
69
+ [-5,11,0,-33,2,15,-8] ].each do |offsets|
70
+ @seq.at(offsets).each_with_index do |val,i|
71
+ val.should eq(@seq.at(offsets[i]))
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '#take' do
80
+ context 'given negative integer' do
81
+ it 'should raise NegativeError' do
82
+ expect { @seq.take(-1) }.to raise_error(NegativeError)
83
+ expect { @seq.take(-10) }.to raise_error(NegativeError)
84
+ end
85
+ end
86
+
87
+ context 'given 0' do
88
+ it 'should return empty array' do
89
+ @seq.take(0).to_a.should eq([])
90
+ end
91
+ end
92
+
93
+ context 'given positive integer' do
94
+ context 'given block' do
95
+ it 'should yield the given number of sequence elements in forward direction (repeating as necessary)' do
96
+ i, m = 0, @pattern.size*2+3
97
+ @seq.take(m) do |n|
98
+ n.should eq(@seq.at(i))
99
+ i += 1
100
+ end
101
+ i.should eq(m)
102
+ end
103
+ end
104
+
105
+ context 'no block given' do
106
+ it 'should return an enumerator' do
107
+ @seq.take(20).should be_a Enumerator
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#take_back' do
114
+ context 'given negative integer' do
115
+ it 'should raise NegativeError' do
116
+ expect { @seq.take_back(-1) }.to raise_error(NegativeError)
117
+ expect { @seq.take_back(-10) }.to raise_error(NegativeError)
118
+ end
119
+ end
120
+
121
+ context 'given 0' do
122
+ it 'should return empty array' do
123
+ @seq.take_back(0).to_a.should eq([])
124
+ end
125
+ end
126
+
127
+ context 'given positive integer' do
128
+ context 'given block' do
129
+ it 'should yield the given number of pattern elements in backward direction (repeating as necessary)' do
130
+ i, m = 0, @pattern.size*2+3
131
+ @seq.take_back(m) do |n|
132
+ n.should eq(@seq.at(i-1))
133
+ i -= 1
134
+ end
135
+ (-i).should eq(m)
136
+ end
137
+ end
138
+
139
+ context 'no block given' do
140
+ it 'should return an enumerator' do
141
+ @seq.take_back(20).should be_a Enumerator
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '#over' do
148
+ context 'given empty (invalid) range' do
149
+ it 'should raise EmptyError' do
150
+ [ 3...-2, 0...0, 5..2, -3..-5 ].each do |range|
151
+ expect { @seq.over(range) }.to raise_error(EmptyError)
152
+ end
153
+ end
154
+ end
155
+
156
+ context 'given range over positive indices' do
157
+ it 'should return seq values at all offsets in range' do
158
+ [ 0..0, 0..2, 1...10, 4..17 ].each do |range|
159
+ vals = @seq.over(range).to_a
160
+ vals2 = range.map {|i| @seq.at(i) }
161
+ vals.should eq(vals2)
162
+ end
163
+ end
164
+ end
165
+
166
+ context 'given negative min and/or max' do
167
+ it 'should return seq values at all offsets in range' do
168
+ [ -5..2, -10..-7, -1...1 ].each do |range|
169
+ vals = @seq.over(range).to_a
170
+ vals2 = range.map {|i| @seq.at(i) }
171
+ vals.should eq(vals2)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end