musicality 0.11.1 → 0.12.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 +5 -5
- data/.coveralls.yml +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +4 -0
- data/ChangeLog.md +11 -0
- data/README.md +3 -0
- data/Rakefile +11 -3
- data/lib/musicality/composition/model/rhythm.rb +33 -0
- data/lib/musicality/composition/model/rhythm_class.rb +30 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_kit.rb +18 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_machine.rb +59 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_parts.rb +21 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_pattern.rb +66 -0
- data/lib/musicality/composition/sequencing/drum_machine/drum_patterns/pop_drum_patterns.rb +146 -0
- data/lib/musicality/composition/sequencing/note_array.rb +33 -0
- data/lib/musicality/composition/sequencing/note_fifo.rb +73 -0
- data/lib/musicality/composition/sequencing/sequenceable.rb +9 -0
- data/lib/musicality/composition/sequencing/sequencer.rb +35 -0
- data/lib/musicality/errors.rb +2 -2
- data/lib/musicality/notation/model/dynamics.rb +2 -2
- data/lib/musicality/notation/model/key.rb +42 -91
- data/lib/musicality/notation/model/keys.rb +35 -34
- data/lib/musicality/notation/model/note.rb +31 -9
- data/lib/musicality/notation/model/pitch.rb +2 -2
- data/lib/musicality/notation/parsing/convenience_methods.rb +23 -12
- data/lib/musicality/notation/parsing/duration_parsing.rb +3 -3
- data/lib/musicality/notation/parsing/key_parsing.rb +150 -0
- data/lib/musicality/notation/parsing/key_parsing.treetop +37 -0
- data/lib/musicality/notation/parsing/meter_parsing.rb +3 -3
- data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +3 -1
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +1 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +1 -1
- data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +4 -1
- data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +1 -1
- data/lib/musicality/notation/parsing/parseable.rb +13 -17
- data/lib/musicality/notation/parsing/pitch_parsing.rb +7 -0
- data/lib/musicality/notation/parsing/segment_parsing.rb +3 -0
- data/lib/musicality/performance/conversion/note_sequence_extractor.rb +82 -134
- data/lib/musicality/performance/model/note_sequence.rb +22 -3
- data/lib/musicality/performance/supercollider/performer.rb +2 -2
- data/lib/musicality/performance/supercollider/sc_drum_kits.rb +29 -0
- data/lib/musicality/performance/supercollider/synthdefs/bass.rb +211 -0
- data/lib/musicality/performance/supercollider/synthdefs/claps.rb +80 -0
- data/lib/musicality/performance/supercollider/synthdefs/cymbals.rb +57 -0
- data/lib/musicality/performance/supercollider/synthdefs/hihats.rb +67 -0
- data/lib/musicality/performance/supercollider/synthdefs/kicks.rb +158 -0
- data/lib/musicality/performance/supercollider/synthdefs/mario.rb +49 -0
- data/lib/musicality/performance/supercollider/{synthdefs.rb → synthdefs/other.rb} +0 -767
- data/lib/musicality/performance/supercollider/synthdefs/pianos.rb +46 -0
- data/lib/musicality/performance/supercollider/synthdefs/snares.rb +169 -0
- data/lib/musicality/performance/supercollider/synthdefs/toms.rb +25 -0
- data/lib/musicality/performance/supercollider/synthdefs/volume.rb +20 -0
- data/lib/musicality/pitch_class.rb +1 -1
- data/lib/musicality/pitch_classes.rb +3 -5
- data/lib/musicality/version.rb +1 -1
- data/lib/musicality.rb +25 -1
- data/musicality.gemspec +3 -2
- data/spec/composition/convenience_methods_spec.rb +8 -8
- data/spec/composition/generation/random_rhythm_generator_spec.rb +5 -5
- data/spec/composition/model/pitch_class_spec.rb +22 -16
- data/spec/composition/model/pitch_classes_spec.rb +5 -5
- data/spec/composition/model/rhythm_class_spec.rb +42 -0
- data/spec/composition/model/rhythm_spec.rb +43 -0
- data/spec/composition/model/scale_class_spec.rb +26 -26
- data/spec/composition/model/scale_spec.rb +38 -38
- data/spec/composition/sequencing/drum_machine/drum_machine_spec.rb +67 -0
- data/spec/composition/sequencing/drum_machine/drum_pattern_spec.rb +58 -0
- data/spec/composition/sequencing/note_array_spec.rb +94 -0
- data/spec/composition/sequencing/note_fifo_spec.rb +183 -0
- data/spec/composition/sequencing/sequencer_spec.rb +76 -0
- data/spec/composition/util/adding_sequence_spec.rb +33 -33
- data/spec/composition/util/compound_sequence_spec.rb +6 -6
- data/spec/composition/util/note_generation_spec.rb +34 -34
- data/spec/composition/util/probabilities_spec.rb +7 -7
- data/spec/composition/util/random_sampler_spec.rb +3 -3
- data/spec/composition/util/repeating_sequence_spec.rb +28 -28
- data/spec/musicality_spec.rb +1 -1
- data/spec/notation/conversion/change_conversion_spec.rb +87 -87
- data/spec/notation/conversion/note_time_converter_spec.rb +22 -22
- data/spec/notation/conversion/score_conversion_spec.rb +1 -1
- data/spec/notation/conversion/score_converter_spec.rb +31 -31
- data/spec/notation/conversion/tempo_conversion_spec.rb +11 -11
- data/spec/notation/model/change_spec.rb +80 -80
- data/spec/notation/model/key_spec.rb +135 -69
- data/spec/notation/model/link_spec.rb +27 -27
- data/spec/notation/model/meter_spec.rb +28 -28
- data/spec/notation/model/note_spec.rb +68 -47
- data/spec/notation/model/part_spec.rb +19 -19
- data/spec/notation/model/pitch_spec.rb +69 -68
- data/spec/notation/model/score_spec.rb +50 -47
- data/spec/notation/parsing/articulation_parsing_spec.rb +4 -4
- data/spec/notation/parsing/convenience_methods_spec.rb +49 -10
- data/spec/notation/parsing/duration_nodes_spec.rb +13 -13
- data/spec/notation/parsing/duration_parsing_spec.rb +10 -10
- data/spec/notation/parsing/key_parsing_spec.rb +19 -0
- data/spec/notation/parsing/link_nodes_spec.rb +7 -7
- data/spec/notation/parsing/link_parsing_spec.rb +4 -4
- data/spec/notation/parsing/meter_parsing_spec.rb +5 -5
- data/spec/notation/parsing/note_node_spec.rb +19 -19
- data/spec/notation/parsing/note_parsing_spec.rb +4 -4
- data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +8 -8
- data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +2 -2
- data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +1 -1
- data/spec/notation/parsing/numbers/positive_float_spec.rb +8 -8
- data/spec/notation/parsing/numbers/positive_integer_spec.rb +6 -6
- data/spec/notation/parsing/numbers/positive_rational_spec.rb +6 -6
- data/spec/notation/parsing/pitch_node_spec.rb +7 -7
- data/spec/notation/parsing/pitch_parsing_spec.rb +2 -2
- data/spec/notation/parsing/segment_parsing_spec.rb +3 -3
- data/spec/notation/util/function_spec.rb +15 -15
- data/spec/notation/util/transition_spec.rb +12 -12
- data/spec/notation/util/value_computer_spec.rb +35 -36
- data/spec/performance/conversion/glissando_converter_spec.rb +24 -24
- data/spec/performance/conversion/note_sequence_extractor_spec.rb +39 -39
- data/spec/performance/conversion/portamento_converter_spec.rb +23 -23
- data/spec/performance/midi/midi_util_spec.rb +41 -41
- data/spec/performance/midi/part_sequencer_spec.rb +10 -10
- data/spec/performance/midi/score_sequencer_spec.rb +15 -15
- data/spec/performance/midi/score_sequencing_spec.rb +2 -2
- data/spec/performance/util/optimization_spec.rb +9 -9
- data/spec/printing/note_engraving_spec.rb +16 -16
- data/spec/printing/score_engraver_spec.rb +5 -5
- data/spec/spec_helper.rb +5 -0
- metadata +85 -30
@@ -12,65 +12,65 @@ describe ScaleClass do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
describe '#intervals' do
|
17
17
|
it 'should return intervals given to #initialize' do
|
18
18
|
valid.each do |intervals|
|
19
|
-
ScaleClass.new(intervals).intervals.
|
19
|
+
expect(ScaleClass.new(intervals).intervals).to eq(intervals)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
describe '#==' do
|
25
25
|
it 'should compare given enumerable to intervals' do
|
26
26
|
valid.each do |intervals|
|
27
27
|
sc = ScaleClass.new(intervals)
|
28
|
-
sc.
|
29
|
-
sc.
|
28
|
+
expect(sc).to eq(intervals)
|
29
|
+
expect(sc).to_not eq(intervals + [2])
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
describe '#rotate' do
|
35
35
|
it 'should return a new ScaleClass, with rotated intervals' do
|
36
36
|
valid.each do |intervals|
|
37
37
|
sc = ScaleClass.new(intervals)
|
38
38
|
[ 0, 1, -1, 4, -3, 2, 6 ].each do |n|
|
39
39
|
sc2 = sc.rotate(n)
|
40
|
-
sc2.
|
41
|
-
sc2.
|
40
|
+
expect(sc2).to_not be(sc)
|
41
|
+
expect(sc2).to eq(intervals.rotate(n))
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
it 'should rotate by 1, by default' do
|
47
47
|
intervals = valid.first
|
48
|
-
ScaleClass.new(intervals).rotate.
|
48
|
+
expect(ScaleClass.new(intervals).rotate).to eq(intervals.rotate(1))
|
49
49
|
end
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
describe '#each' do
|
53
53
|
before :all do
|
54
54
|
@sc = ScaleClass.new(valid.first)
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
context 'block given' do
|
58
58
|
it 'should yield all interval values' do
|
59
59
|
xs = []
|
60
60
|
@sc.each do |x|
|
61
61
|
xs.push(x)
|
62
62
|
end
|
63
|
-
xs.
|
63
|
+
expect(xs).to eq(@sc.intervals)
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
context 'no block given' do
|
68
68
|
it 'should return an enumerator' do
|
69
|
-
@sc.each.
|
69
|
+
expect(@sc.each).to be_a Enumerator
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
describe '#to_pitch_seq' do
|
75
75
|
before :all do
|
76
76
|
@sc = ScaleClass.new([2,2,1,2,2,2,1])
|
@@ -79,20 +79,20 @@ describe ScaleClass do
|
|
79
79
|
@prev_octave = [C3,D3,E3,F3,G3,A3,B3,C4]
|
80
80
|
@pseq = @sc.to_pitch_seq(@start_pitch)
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
it 'should return a AddingSequence' do
|
84
|
-
@pseq.
|
84
|
+
expect(@pseq).to be_a AddingSequence
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
it 'should be centered at given start pitch' do
|
88
|
-
@pseq.at(0).
|
88
|
+
expect(@pseq.at(0)).to eq(@start_pitch)
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
it 'should walk forward/backward through scale' do
|
92
|
-
@pseq.take(8).to_a.
|
93
|
-
@pseq.over(0...8).to_a.
|
94
|
-
@pseq.take_back(7).to_a.
|
95
|
-
@pseq.over(-7..0).to_a.
|
92
|
+
expect(@pseq.take(8).to_a).to eq(@first_octave)
|
93
|
+
expect(@pseq.over(0...8).to_a).to eq(@first_octave)
|
94
|
+
expect(@pseq.take_back(7).to_a).to eq(@prev_octave.reverse.drop(1))
|
95
|
+
expect(@pseq.over(-7..0).to_a).to eq(@prev_octave)
|
96
96
|
end
|
97
97
|
end
|
98
|
-
end
|
98
|
+
end
|
@@ -11,16 +11,16 @@ describe Scale do
|
|
11
11
|
|
12
12
|
describe '#pitch_class' do
|
13
13
|
it 'should return the pitch class given to #initialize' do
|
14
|
-
@scale.pitch_class.
|
14
|
+
expect(@scale.pitch_class).to eq(@pc)
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
describe '#size' do
|
19
19
|
it 'should return the size of the scale intervals' do
|
20
|
-
@scale.size.
|
20
|
+
expect(@scale.size).to eq(@intervals.size)
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
describe '#transpose' do
|
25
25
|
before :all do
|
26
26
|
@diff2 = 3
|
@@ -28,25 +28,25 @@ describe Scale do
|
|
28
28
|
@diff3 = -5
|
29
29
|
@scale3 = @scale.transpose(@diff3)
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
it 'should return a new Scale' do
|
33
|
-
@scale2.
|
34
|
-
@scale2.
|
35
|
-
@scale3.
|
36
|
-
@scale3.
|
33
|
+
expect(@scale2).to be_a Scale
|
34
|
+
expect(@scale2).to_not be @scale
|
35
|
+
expect(@scale3).to be_a Scale
|
36
|
+
expect(@scale3).to_not be @scale
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
it 'should return a scale with same intervals' do
|
40
|
-
@scale2.intervals.
|
41
|
-
@scale3.intervals.
|
40
|
+
expect(@scale2.intervals).to eq @scale.intervals
|
41
|
+
expect(@scale3.intervals).to eq @scale.intervals
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
it 'should return a scale with a shifted pitch class' do
|
45
|
-
@scale2.pitch_class.
|
46
|
-
@scale3.pitch_class.
|
45
|
+
expect(@scale2.pitch_class).to eq((@scale.pitch_class + @diff2).to_pc)
|
46
|
+
expect(@scale3.pitch_class).to eq((@scale.pitch_class + @diff3).to_pc)
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
describe '#rotate' do
|
51
51
|
before :all do
|
52
52
|
@n2 = 5
|
@@ -54,57 +54,57 @@ describe Scale do
|
|
54
54
|
@n3 = -3
|
55
55
|
@scale3 = @scale.rotate(@n3)
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
it 'should return a new Scale' do
|
59
|
-
@scale2.
|
60
|
-
@scale2.
|
61
|
-
@scale3.
|
62
|
-
@scale3.
|
59
|
+
expect(@scale2).to be_a Scale
|
60
|
+
expect(@scale2).to_not be @scale
|
61
|
+
expect(@scale3).to be_a Scale
|
62
|
+
expect(@scale3).to_not be @scale
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
it 'should return a scale with rotated intervals' do
|
66
|
-
@scale2.intervals.
|
67
|
-
@scale3.intervals.
|
66
|
+
expect(@scale2.intervals).to eq @scale.intervals.rotate(@n2)
|
67
|
+
expect(@scale3.intervals).to eq @scale.intervals.rotate(@n3)
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
it 'should return a scale with a shifted pitch class' do
|
71
71
|
pc2 = (AddingSequence.new(@scale.intervals).at(@n2) + @scale.pitch_class).to_pc
|
72
|
-
@scale2.pitch_class.
|
72
|
+
expect(@scale2.pitch_class).to eq(pc2)
|
73
73
|
pc3 = (AddingSequence.new(@scale.intervals).at(@n3) + @scale.pitch_class).to_pc
|
74
|
-
@scale3.pitch_class.
|
74
|
+
expect(@scale3.pitch_class).to eq(pc3)
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
describe '#at_octave' do
|
79
79
|
before :all do
|
80
80
|
@octave = 2
|
81
81
|
@pitch_seq = @scale.at_octave(@octave)
|
82
82
|
@start_pitch = @pitch_seq.at(0)
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
it 'should return a bi-infinite sequence' do
|
86
|
-
@pitch_seq.
|
86
|
+
expect(@pitch_seq).to be_a BiInfiniteSequence
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
it 'should start sequence at scale pitch class and given octave' do
|
90
|
-
@start_pitch.semitone.
|
91
|
-
@start_pitch.octave.
|
90
|
+
expect(@start_pitch.semitone).to eq(@scale.pitch_class)
|
91
|
+
expect(@start_pitch.octave).to eq(@octave)
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
it 'should make sequence that proceeds forwards along scale intervals' do
|
95
95
|
first_pitches = @pitch_seq.over(0..@scale.intervals.size).to_a
|
96
96
|
first_pitches[1..-1].each_with_index do |pitch,i|
|
97
97
|
diff = pitch.diff(first_pitches[i])
|
98
|
-
diff.
|
98
|
+
expect(diff).to eq(@scale.intervals[i])
|
99
99
|
end
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
it 'should make sequence that proceeds backwards along scale intervals' do
|
103
103
|
first_pitches = @pitch_seq.over(-@scale.intervals.size..0).to_a
|
104
104
|
first_pitches[1..-1].each_with_index do |pitch,i|
|
105
105
|
diff = pitch.diff(first_pitches[i])
|
106
|
-
diff.
|
106
|
+
expect(diff).to eq(@scale.intervals[i])
|
107
107
|
end
|
108
108
|
end
|
109
109
|
end
|
110
|
-
end
|
110
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
|
+
|
3
|
+
describe DrumMachine do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'given no patterns' do
|
6
|
+
it 'should raise ArgumentError' do
|
7
|
+
expect { DrumMachine.new([]) }.to raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'given 1 pattern with 2 parts' do
|
12
|
+
before :all do
|
13
|
+
@pattern = DrumPattern.new(1, DrumParts::SNARE_DRUM => [1,2,1], DrumParts::CLOSED_HI_HAT => [1,1])
|
14
|
+
@dm = DrumMachine.new([@pattern])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should produce a NoteArray sequenceables for each part in pattern' do
|
18
|
+
expect(@dm.part_sequenceables).to have_key(DrumParts::SNARE_DRUM)
|
19
|
+
expect(@dm.part_sequenceables[DrumParts::SNARE_DRUM]).to be_a NoteArray
|
20
|
+
|
21
|
+
expect(@dm.part_sequenceables).to have_key(DrumParts::CLOSED_HI_HAT)
|
22
|
+
expect(@dm.part_sequenceables[DrumParts::CLOSED_HI_HAT]).to be_a NoteArray
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should provide part notes from the pattern to the NoteArray' do
|
26
|
+
expect(@dm.part_sequenceables[DrumParts::SNARE_DRUM].notes).to eq(@pattern.part_notes[DrumParts::SNARE_DRUM])
|
27
|
+
expect(@dm.part_sequenceables[DrumParts::CLOSED_HI_HAT].notes).to eq(@pattern.part_notes[DrumParts::CLOSED_HI_HAT])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'given 2 patterns' do
|
32
|
+
context 'first pattern contains only part DrumParts::SNARE_DRUM, second pattern contains only part DrumParts::CLOSED_HI_HAT' do
|
33
|
+
before :all do
|
34
|
+
@pattern1 = DrumPattern.new(2, DrumParts::SNARE_DRUM => [1,2,1])
|
35
|
+
@pattern2 = DrumPattern.new(1, DrumParts::CLOSED_HI_HAT => [1,1])
|
36
|
+
@dm = DrumMachine.new([@pattern1, @pattern2])
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should produce a NoteArray sequenceables for each part in pattern' do
|
40
|
+
expect(@dm.part_sequenceables).to have_key(DrumParts::SNARE_DRUM)
|
41
|
+
expect(@dm.part_sequenceables[DrumParts::SNARE_DRUM]).to be_a NoteArray
|
42
|
+
|
43
|
+
expect(@dm.part_sequenceables).to have_key(DrumParts::CLOSED_HI_HAT)
|
44
|
+
expect(@dm.part_sequenceables[DrumParts::CLOSED_HI_HAT]).to be_a NoteArray
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should start part DrumParts::SNARE_DRUM with notes from the first pattern' do
|
48
|
+
first_notes = @pattern1.part_notes[DrumParts::SNARE_DRUM]
|
49
|
+
expect(@dm.part_sequenceables[DrumParts::SNARE_DRUM].notes.slice(0...-1)).to eq(first_notes)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should end part DrumParts::CLOSED_HI_HAT with notes from the second pattern' do
|
53
|
+
last_notes = @pattern2.part_notes[DrumParts::CLOSED_HI_HAT]
|
54
|
+
expect(@dm.part_sequenceables[DrumParts::CLOSED_HI_HAT].notes.slice(1..-1)).to eq(last_notes)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should start part DrumParts::CLOSED_HI_HAT with a rest note that has same duration as the first pattern' do
|
58
|
+
expect(@dm.part_sequenceables[DrumParts::CLOSED_HI_HAT].notes.first).to eq(Note.new(@pattern1.duration))
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should end part DrumParts::SNARE_DRUM with a rest note that has same duration as the second pattern' do
|
62
|
+
expect(@dm.part_sequenceables[DrumParts::SNARE_DRUM].notes.last).to eq(Note.new(@pattern2.duration))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
|
+
|
3
|
+
def notes_duration(notes)
|
4
|
+
notes.map {|note| note.duration }.inject(0, :+)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe DrumPattern do
|
8
|
+
describe '#initialize' do
|
9
|
+
context 'no part_name_portions given' do
|
10
|
+
it 'should produce no part_notes' do
|
11
|
+
expect(DrumPattern.new(0, {}).part_notes).to be_empty
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'one part_name_portions given' do
|
16
|
+
context 'total duration of 0 given' do
|
17
|
+
it 'should raise ArgumentError' do
|
18
|
+
expect { DrumPattern.new(0, DrumParts::SNARE_DRUM => [1,1]) }.to raise_error(ArgumentError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'total duration of 1 given' do
|
23
|
+
it 'should produce 1 part_notes with total duration of 1' do
|
24
|
+
dp = DrumPattern.new(1, DrumParts::SNARE_DRUM => [1,1])
|
25
|
+
expect(dp.part_notes.size).to eq(1)
|
26
|
+
expect(dp.part_notes.keys[0]).to eq(DrumParts::SNARE_DRUM)
|
27
|
+
expect(notes_duration(dp.part_notes.values[0])).to eq(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'multiple part_name_portions given' do
|
33
|
+
context 'total duration of 2 given' do
|
34
|
+
before :all do
|
35
|
+
@part_name_portions = {DrumParts::SNARE_DRUM => [1,1], DrumParts::CLOSED_HI_HAT => [1,1,3]}
|
36
|
+
@duration = 2
|
37
|
+
@dp = DrumPattern.new(@duration, @part_name_portions)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should produce as many part_notes as were given' do
|
41
|
+
expect(@dp.part_notes.size).to eq(@part_name_portions.size)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should produce part_notes that match given part_name_portions' do
|
45
|
+
@part_name_portions.keys.each do |part_name|
|
46
|
+
expect(@dp.part_notes).to have_key(part_name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should produce all part_notes with total duration equal to that of pattern' do
|
51
|
+
@dp.part_notes.values.each do |notes|
|
52
|
+
expect(notes_duration(notes)).to eq(@duration)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe NoteArray do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'given empty array' do
|
6
|
+
it 'should raise ArgumentError' do
|
7
|
+
expect { NoteArray.new([]) }.to raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#next_note' do
|
13
|
+
context '1 note in array' do
|
14
|
+
before :each do
|
15
|
+
@note = Note.whole
|
16
|
+
@note_array = NoteArray.new([@note])
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'next_note not called yet' do
|
20
|
+
it 'should produce the only note in the array' do
|
21
|
+
expect(@note_array.next_note).to eq(@note)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'next already called once' do
|
26
|
+
it 'should produce the only note array' do
|
27
|
+
@note_array.next_note
|
28
|
+
expect(@note_array.next_note).to eq(@note)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context '2 notes in array' do
|
34
|
+
before :each do
|
35
|
+
@notes = [ Note.whole, Note.quarter ]
|
36
|
+
@note_array = NoteArray.new(@notes)
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'next_note not called yet' do
|
40
|
+
it 'should produce the first note' do
|
41
|
+
expect(@note_array.next_note).to eq(@notes[0])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'next_note already called once' do
|
46
|
+
it 'should produce the second note' do
|
47
|
+
@note_array.next_note
|
48
|
+
expect(@note_array.next_note).to eq(@notes[1])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'next_note already called twice' do
|
53
|
+
it 'should produce the first note' do
|
54
|
+
@note_array.next_note
|
55
|
+
@note_array.next_note
|
56
|
+
expect(@note_array.next_note).to eq(@notes[0])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#reset' do
|
63
|
+
context '3 notes in array' do
|
64
|
+
before :each do
|
65
|
+
@notes = [ Note.eighth, Note.eighth, Note.quarter ]
|
66
|
+
@note_array = NoteArray.new(@notes)
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'next_note not called yet' do
|
70
|
+
it 'should have no effect, so a follow-up call to #next_note still returns first note' do
|
71
|
+
@note_array.reset
|
72
|
+
expect(@note_array.next_note).to eq(@notes[0])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'next_note already called once' do
|
77
|
+
it 'should result in a follow-up call to #next_note returning the first note' do
|
78
|
+
@note_array.next_note
|
79
|
+
@note_array.reset
|
80
|
+
expect(@note_array.next_note).to eq(@notes[0])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'next_note already called twice' do
|
85
|
+
it 'should result in a follow-up call to #next_note returning the first note' do
|
86
|
+
@note_array.next_note
|
87
|
+
@note_array.next_note
|
88
|
+
@note_array.reset
|
89
|
+
expect(@note_array.next_note).to eq(@notes[0])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Sequencer::NoteFIFO do
|
4
|
+
describe '#initialize' do
|
5
|
+
it 'should add the given notes to the FIFO' do
|
6
|
+
notes = [Note.half, Note.quarter]
|
7
|
+
fifo = Sequencer::NoteFIFO.new(notes)
|
8
|
+
expect(fifo.notes).to eq(notes)
|
9
|
+
expect(fifo.duration).to eq(Rational(3,4))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#empty?' do
|
14
|
+
context 'FIFO does not have notes' do
|
15
|
+
it 'should return true' do
|
16
|
+
# initialized without notes
|
17
|
+
expect(Sequencer::NoteFIFO.new.empty?).to be_truthy
|
18
|
+
|
19
|
+
# or removed later
|
20
|
+
fifo = Sequencer::NoteFIFO.new
|
21
|
+
fifo.remove_notes(fifo.duration)
|
22
|
+
expect(fifo.empty?).to be_truthy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'FIFO has notes' do
|
27
|
+
it 'should return false' do
|
28
|
+
# initialized with notes
|
29
|
+
notes = [Note.whole, Note.half, Note.quarter]
|
30
|
+
expect(Sequencer::NoteFIFO.new(notes).empty?).to be_falsey
|
31
|
+
|
32
|
+
# or added later
|
33
|
+
fifo = Sequencer::NoteFIFO.new
|
34
|
+
fifo.add_notes(notes)
|
35
|
+
expect(fifo.empty?).to be_falsey
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#add_notes' do
|
41
|
+
context 'given empty array' do
|
42
|
+
before :all do
|
43
|
+
@notes = [Note.whole, Note.half, Note.quarter]
|
44
|
+
@fifo = Sequencer::NoteFIFO.new(@notes)
|
45
|
+
@prev_duration = @fifo.duration
|
46
|
+
@fifo.add_notes([])
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should not change the current note array' do
|
50
|
+
expect(@fifo.notes).to eq(@notes)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not change the duration' do
|
54
|
+
expect(@fifo.duration).to eq(@prev_duration)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'given array of one note' do
|
59
|
+
before :all do
|
60
|
+
@notes = [Note.quarter, Note.half]
|
61
|
+
@fifo = Sequencer::NoteFIFO.new(@notes)
|
62
|
+
@prev_duration = @fifo.duration
|
63
|
+
@new_note = Note.whole
|
64
|
+
@fifo.add_notes([@new_note])
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should append the given note to the FIFO note array' do
|
68
|
+
expect(@fifo.notes).to eq(@notes + [@new_note])
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should increase the FIFO duration by the total duration of the given note' do
|
72
|
+
expect(@fifo.duration).to eq(@prev_duration + @new_note.duration)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'given an array of more than one note' do
|
77
|
+
before :all do
|
78
|
+
@notes = [Note.quarter, Note.half]
|
79
|
+
@fifo = Sequencer::NoteFIFO.new(@notes)
|
80
|
+
@prev_duration = @fifo.duration
|
81
|
+
@new_notes = [ Note.whole, Note.quarter ]
|
82
|
+
@fifo.add_notes(@new_notes)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should append the given notes to the FIFO note array' do
|
86
|
+
expect(@fifo.notes).to eq(@notes + @new_notes)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should increase the FIFO duration by the total duration of the given notes' do
|
90
|
+
add_dur = @new_notes.inject(0){|sum, n| sum + n.duration }
|
91
|
+
expect(@fifo.duration).to eq(@prev_duration + add_dur)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#remove_notes' do
|
97
|
+
before :each do
|
98
|
+
@notes = [Note.whole, Note.half, Note.quarter]
|
99
|
+
@fifo = Sequencer::NoteFIFO.new(@notes)
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'given negative target duration' do
|
103
|
+
it 'should raise ArgumentError' do
|
104
|
+
expect { @fifo.remove_notes(-1) }.to raise_error(ArgumentError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'given target duration greater than FIFO duration' do
|
109
|
+
it 'should raise ArgumentError' do
|
110
|
+
expect { @fifo.remove_notes(@fifo.duration + 1) }.to raise_error(ArgumentError)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'given target_duration of 0' do
|
115
|
+
it 'should return an empty array and not affect FIFO' do
|
116
|
+
removed_notes = @fifo.remove_notes(0)
|
117
|
+
expect(removed_notes).to be_empty
|
118
|
+
expect(@fifo.notes).to eq(@notes)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'given target duration equal to FIFO duration' do
|
123
|
+
it 'should empty FIFO and bring duration to 0' do
|
124
|
+
@fifo.remove_notes(@fifo.duration)
|
125
|
+
expect(@fifo.empty?).to be_truthy
|
126
|
+
expect(@fifo.duration).to eq(0)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'given positive target duration less than FIFO duration' do
|
131
|
+
context 'target duration lands exactly on note boundary' do
|
132
|
+
before :all do
|
133
|
+
@notes = [Note.whole, Note.half, Note.quarter]
|
134
|
+
@fifo = Sequencer::NoteFIFO.new(@notes)
|
135
|
+
@prev_fifo_dur = @fifo.duration
|
136
|
+
@target_dur = 1.5
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should reduce FIFO duration by the given target duration' do
|
140
|
+
@fifo.remove_notes(@target_dur)
|
141
|
+
expect(@fifo.duration).to eq(@prev_fifo_dur - @target_dur)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should remove notes from the front of the FIFO, totally in tact' do
|
145
|
+
removed = @fifo.remove_notes(@target_dur)
|
146
|
+
expect(removed).to eq(@notes[0..1])
|
147
|
+
expect(@fifo.notes).to eq([@notes.last])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'target duration lands somewhere during one of the notes' do
|
152
|
+
before :each do
|
153
|
+
@notes = [Note.whole, Note.half([Pitches::C4]), Note.quarter]
|
154
|
+
@fifo = Sequencer::NoteFIFO.new(@notes)
|
155
|
+
@prev_fifo_dur = @fifo.duration
|
156
|
+
@target_dur = 1.25
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should reduce FIFO duration by the given target duration' do
|
160
|
+
@fifo.remove_notes(@target_dur)
|
161
|
+
expect(@fifo.duration).to eq(@prev_fifo_dur - @target_dur)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should remove notes from the front of the FIFO, dividing the last one into two tied notes' do
|
165
|
+
expected_removed = [
|
166
|
+
Note.whole,
|
167
|
+
Note.quarter([Pitches::C4], links: { Pitches::C4 => Link::Tie.new })
|
168
|
+
]
|
169
|
+
|
170
|
+
expected_remaining = [
|
171
|
+
Note.quarter([Pitches::C4]),
|
172
|
+
Note.quarter
|
173
|
+
]
|
174
|
+
|
175
|
+
removed = @fifo.remove_notes(@target_dur)
|
176
|
+
|
177
|
+
expect(removed).to eq(expected_removed)
|
178
|
+
expect(@fifo.notes).to eq(expected_remaining)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|