musicality 0.3.0 → 0.5.0

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