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.
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