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