musicality 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +4 -0
  5. data/ChangeLog.md +11 -0
  6. data/README.md +3 -0
  7. data/Rakefile +11 -3
  8. data/lib/musicality/composition/model/rhythm.rb +33 -0
  9. data/lib/musicality/composition/model/rhythm_class.rb +30 -0
  10. data/lib/musicality/composition/sequencing/drum_machine/drum_kit.rb +18 -0
  11. data/lib/musicality/composition/sequencing/drum_machine/drum_machine.rb +59 -0
  12. data/lib/musicality/composition/sequencing/drum_machine/drum_parts.rb +21 -0
  13. data/lib/musicality/composition/sequencing/drum_machine/drum_pattern.rb +66 -0
  14. data/lib/musicality/composition/sequencing/drum_machine/drum_patterns/pop_drum_patterns.rb +146 -0
  15. data/lib/musicality/composition/sequencing/note_array.rb +33 -0
  16. data/lib/musicality/composition/sequencing/note_fifo.rb +73 -0
  17. data/lib/musicality/composition/sequencing/sequenceable.rb +9 -0
  18. data/lib/musicality/composition/sequencing/sequencer.rb +35 -0
  19. data/lib/musicality/errors.rb +2 -2
  20. data/lib/musicality/notation/model/dynamics.rb +2 -2
  21. data/lib/musicality/notation/model/key.rb +42 -91
  22. data/lib/musicality/notation/model/keys.rb +35 -34
  23. data/lib/musicality/notation/model/note.rb +31 -9
  24. data/lib/musicality/notation/model/pitch.rb +2 -2
  25. data/lib/musicality/notation/parsing/convenience_methods.rb +23 -12
  26. data/lib/musicality/notation/parsing/duration_parsing.rb +3 -3
  27. data/lib/musicality/notation/parsing/key_parsing.rb +150 -0
  28. data/lib/musicality/notation/parsing/key_parsing.treetop +37 -0
  29. data/lib/musicality/notation/parsing/meter_parsing.rb +3 -3
  30. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +3 -1
  31. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +1 -0
  32. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +1 -1
  33. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +4 -1
  34. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +1 -1
  35. data/lib/musicality/notation/parsing/parseable.rb +13 -17
  36. data/lib/musicality/notation/parsing/pitch_parsing.rb +7 -0
  37. data/lib/musicality/notation/parsing/segment_parsing.rb +3 -0
  38. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +82 -134
  39. data/lib/musicality/performance/model/note_sequence.rb +22 -3
  40. data/lib/musicality/performance/supercollider/performer.rb +2 -2
  41. data/lib/musicality/performance/supercollider/sc_drum_kits.rb +29 -0
  42. data/lib/musicality/performance/supercollider/synthdefs/bass.rb +211 -0
  43. data/lib/musicality/performance/supercollider/synthdefs/claps.rb +80 -0
  44. data/lib/musicality/performance/supercollider/synthdefs/cymbals.rb +57 -0
  45. data/lib/musicality/performance/supercollider/synthdefs/hihats.rb +67 -0
  46. data/lib/musicality/performance/supercollider/synthdefs/kicks.rb +158 -0
  47. data/lib/musicality/performance/supercollider/synthdefs/mario.rb +49 -0
  48. data/lib/musicality/performance/supercollider/{synthdefs.rb → synthdefs/other.rb} +0 -767
  49. data/lib/musicality/performance/supercollider/synthdefs/pianos.rb +46 -0
  50. data/lib/musicality/performance/supercollider/synthdefs/snares.rb +169 -0
  51. data/lib/musicality/performance/supercollider/synthdefs/toms.rb +25 -0
  52. data/lib/musicality/performance/supercollider/synthdefs/volume.rb +20 -0
  53. data/lib/musicality/pitch_class.rb +1 -1
  54. data/lib/musicality/pitch_classes.rb +3 -5
  55. data/lib/musicality/version.rb +1 -1
  56. data/lib/musicality.rb +25 -1
  57. data/musicality.gemspec +3 -2
  58. data/spec/composition/convenience_methods_spec.rb +8 -8
  59. data/spec/composition/generation/random_rhythm_generator_spec.rb +5 -5
  60. data/spec/composition/model/pitch_class_spec.rb +22 -16
  61. data/spec/composition/model/pitch_classes_spec.rb +5 -5
  62. data/spec/composition/model/rhythm_class_spec.rb +42 -0
  63. data/spec/composition/model/rhythm_spec.rb +43 -0
  64. data/spec/composition/model/scale_class_spec.rb +26 -26
  65. data/spec/composition/model/scale_spec.rb +38 -38
  66. data/spec/composition/sequencing/drum_machine/drum_machine_spec.rb +67 -0
  67. data/spec/composition/sequencing/drum_machine/drum_pattern_spec.rb +58 -0
  68. data/spec/composition/sequencing/note_array_spec.rb +94 -0
  69. data/spec/composition/sequencing/note_fifo_spec.rb +183 -0
  70. data/spec/composition/sequencing/sequencer_spec.rb +76 -0
  71. data/spec/composition/util/adding_sequence_spec.rb +33 -33
  72. data/spec/composition/util/compound_sequence_spec.rb +6 -6
  73. data/spec/composition/util/note_generation_spec.rb +34 -34
  74. data/spec/composition/util/probabilities_spec.rb +7 -7
  75. data/spec/composition/util/random_sampler_spec.rb +3 -3
  76. data/spec/composition/util/repeating_sequence_spec.rb +28 -28
  77. data/spec/musicality_spec.rb +1 -1
  78. data/spec/notation/conversion/change_conversion_spec.rb +87 -87
  79. data/spec/notation/conversion/note_time_converter_spec.rb +22 -22
  80. data/spec/notation/conversion/score_conversion_spec.rb +1 -1
  81. data/spec/notation/conversion/score_converter_spec.rb +31 -31
  82. data/spec/notation/conversion/tempo_conversion_spec.rb +11 -11
  83. data/spec/notation/model/change_spec.rb +80 -80
  84. data/spec/notation/model/key_spec.rb +135 -69
  85. data/spec/notation/model/link_spec.rb +27 -27
  86. data/spec/notation/model/meter_spec.rb +28 -28
  87. data/spec/notation/model/note_spec.rb +68 -47
  88. data/spec/notation/model/part_spec.rb +19 -19
  89. data/spec/notation/model/pitch_spec.rb +69 -68
  90. data/spec/notation/model/score_spec.rb +50 -47
  91. data/spec/notation/parsing/articulation_parsing_spec.rb +4 -4
  92. data/spec/notation/parsing/convenience_methods_spec.rb +49 -10
  93. data/spec/notation/parsing/duration_nodes_spec.rb +13 -13
  94. data/spec/notation/parsing/duration_parsing_spec.rb +10 -10
  95. data/spec/notation/parsing/key_parsing_spec.rb +19 -0
  96. data/spec/notation/parsing/link_nodes_spec.rb +7 -7
  97. data/spec/notation/parsing/link_parsing_spec.rb +4 -4
  98. data/spec/notation/parsing/meter_parsing_spec.rb +5 -5
  99. data/spec/notation/parsing/note_node_spec.rb +19 -19
  100. data/spec/notation/parsing/note_parsing_spec.rb +4 -4
  101. data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +8 -8
  102. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +2 -2
  103. data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +1 -1
  104. data/spec/notation/parsing/numbers/positive_float_spec.rb +8 -8
  105. data/spec/notation/parsing/numbers/positive_integer_spec.rb +6 -6
  106. data/spec/notation/parsing/numbers/positive_rational_spec.rb +6 -6
  107. data/spec/notation/parsing/pitch_node_spec.rb +7 -7
  108. data/spec/notation/parsing/pitch_parsing_spec.rb +2 -2
  109. data/spec/notation/parsing/segment_parsing_spec.rb +3 -3
  110. data/spec/notation/util/function_spec.rb +15 -15
  111. data/spec/notation/util/transition_spec.rb +12 -12
  112. data/spec/notation/util/value_computer_spec.rb +35 -36
  113. data/spec/performance/conversion/glissando_converter_spec.rb +24 -24
  114. data/spec/performance/conversion/note_sequence_extractor_spec.rb +39 -39
  115. data/spec/performance/conversion/portamento_converter_spec.rb +23 -23
  116. data/spec/performance/midi/midi_util_spec.rb +41 -41
  117. data/spec/performance/midi/part_sequencer_spec.rb +10 -10
  118. data/spec/performance/midi/score_sequencer_spec.rb +15 -15
  119. data/spec/performance/midi/score_sequencing_spec.rb +2 -2
  120. data/spec/performance/util/optimization_spec.rb +9 -9
  121. data/spec/printing/note_engraving_spec.rb +16 -16
  122. data/spec/printing/score_engraver_spec.rb +5 -5
  123. data/spec/spec_helper.rb +5 -0
  124. 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.should eq(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.should eq(intervals)
29
- sc.should_not eq(intervals + [2])
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.should_not be(sc)
41
- sc2.should eq(intervals.rotate(n))
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.should eq(intervals.rotate(1))
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.should eq(@sc.intervals)
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.should be_a Enumerator
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.should be_a AddingSequence
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).should eq(@start_pitch)
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.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)
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.should eq(@pc)
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.should eq(@intervals.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.should be_a Scale
34
- @scale2.should_not be @scale
35
- @scale3.should be_a Scale
36
- @scale3.should_not be @scale
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.should eq @scale.intervals
41
- @scale3.intervals.should eq @scale.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.should eq((@scale.pitch_class + @diff2).to_pc)
46
- @scale3.pitch_class.should eq((@scale.pitch_class + @diff3).to_pc)
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.should be_a Scale
60
- @scale2.should_not be @scale
61
- @scale3.should be_a Scale
62
- @scale3.should_not be @scale
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.should eq @scale.intervals.rotate(@n2)
67
- @scale3.intervals.should eq @scale.intervals.rotate(@n3)
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.should eq(pc2)
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.should eq(pc3)
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.should be_a BiInfiniteSequence
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.should eq(@scale.pitch_class)
91
- @start_pitch.octave.should eq(@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.should eq(@scale.intervals[i])
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.should eq(@scale.intervals[i])
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