musicality 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +47 -0
  8. data/Rakefile +65 -0
  9. data/bin/midify +78 -0
  10. data/examples/hip.rb +32 -0
  11. data/examples/missed_connection.rb +26 -0
  12. data/examples/song1.rb +33 -0
  13. data/examples/song2.rb +32 -0
  14. data/lib/musicality/errors.rb +9 -0
  15. data/lib/musicality/notation/conversion/change_conversion.rb +19 -0
  16. data/lib/musicality/notation/conversion/measure_note_map.rb +40 -0
  17. data/lib/musicality/notation/conversion/measured_score_conversion.rb +70 -0
  18. data/lib/musicality/notation/conversion/measured_score_converter.rb +95 -0
  19. data/lib/musicality/notation/conversion/note_time_converter.rb +68 -0
  20. data/lib/musicality/notation/conversion/tempo_conversion.rb +25 -0
  21. data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +47 -0
  22. data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +64 -0
  23. data/lib/musicality/notation/model/articulations.rb +13 -0
  24. data/lib/musicality/notation/model/change.rb +62 -0
  25. data/lib/musicality/notation/model/dynamics.rb +12 -0
  26. data/lib/musicality/notation/model/link.rb +73 -0
  27. data/lib/musicality/notation/model/meter.rb +54 -0
  28. data/lib/musicality/notation/model/meters.rb +9 -0
  29. data/lib/musicality/notation/model/note.rb +120 -0
  30. data/lib/musicality/notation/model/part.rb +54 -0
  31. data/lib/musicality/notation/model/pitch.rb +163 -0
  32. data/lib/musicality/notation/model/pitches.rb +21 -0
  33. data/lib/musicality/notation/model/program.rb +53 -0
  34. data/lib/musicality/notation/model/score.rb +132 -0
  35. data/lib/musicality/notation/packing/change_packing.rb +46 -0
  36. data/lib/musicality/notation/packing/part_packing.rb +31 -0
  37. data/lib/musicality/notation/packing/program_packing.rb +16 -0
  38. data/lib/musicality/notation/packing/score_packing.rb +108 -0
  39. data/lib/musicality/notation/parsing/articulation_parsing.rb +264 -0
  40. data/lib/musicality/notation/parsing/articulation_parsing.treetop +59 -0
  41. data/lib/musicality/notation/parsing/convenience_methods.rb +74 -0
  42. data/lib/musicality/notation/parsing/duration_nodes.rb +21 -0
  43. data/lib/musicality/notation/parsing/duration_parsing.rb +205 -0
  44. data/lib/musicality/notation/parsing/duration_parsing.treetop +25 -0
  45. data/lib/musicality/notation/parsing/link_nodes.rb +35 -0
  46. data/lib/musicality/notation/parsing/link_parsing.rb +270 -0
  47. data/lib/musicality/notation/parsing/link_parsing.treetop +33 -0
  48. data/lib/musicality/notation/parsing/meter_parsing.rb +190 -0
  49. data/lib/musicality/notation/parsing/meter_parsing.treetop +29 -0
  50. data/lib/musicality/notation/parsing/note_node.rb +40 -0
  51. data/lib/musicality/notation/parsing/note_parsing.rb +229 -0
  52. data/lib/musicality/notation/parsing/note_parsing.treetop +28 -0
  53. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +289 -0
  54. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.treetop +29 -0
  55. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +64 -0
  56. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +17 -0
  57. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +86 -0
  58. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.treetop +20 -0
  59. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +503 -0
  60. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.treetop +33 -0
  61. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +95 -0
  62. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +19 -0
  63. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +84 -0
  64. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.treetop +19 -0
  65. data/lib/musicality/notation/parsing/parseable.rb +30 -0
  66. data/lib/musicality/notation/parsing/pitch_node.rb +23 -0
  67. data/lib/musicality/notation/parsing/pitch_parsing.rb +448 -0
  68. data/lib/musicality/notation/parsing/pitch_parsing.treetop +52 -0
  69. data/lib/musicality/notation/parsing/segment_parsing.rb +141 -0
  70. data/lib/musicality/notation/parsing/segment_parsing.treetop +23 -0
  71. data/lib/musicality/notation/util/interpolation.rb +16 -0
  72. data/lib/musicality/notation/util/piecewise_function.rb +122 -0
  73. data/lib/musicality/notation/util/value_computer.rb +170 -0
  74. data/lib/musicality/performance/conversion/glissando_converter.rb +34 -0
  75. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +98 -0
  76. data/lib/musicality/performance/conversion/portamento_converter.rb +24 -0
  77. data/lib/musicality/performance/conversion/score_collator.rb +126 -0
  78. data/lib/musicality/performance/midi/midi_events.rb +34 -0
  79. data/lib/musicality/performance/midi/midi_util.rb +31 -0
  80. data/lib/musicality/performance/midi/part_sequencer.rb +123 -0
  81. data/lib/musicality/performance/midi/score_sequencer.rb +45 -0
  82. data/lib/musicality/performance/model/note_attacks.rb +19 -0
  83. data/lib/musicality/performance/model/note_sequence.rb +111 -0
  84. data/lib/musicality/performance/util/note_linker.rb +28 -0
  85. data/lib/musicality/performance/util/optimization.rb +31 -0
  86. data/lib/musicality/validatable.rb +38 -0
  87. data/lib/musicality/version.rb +3 -0
  88. data/lib/musicality.rb +81 -0
  89. data/musicality.gemspec +30 -0
  90. data/spec/musicality_spec.rb +7 -0
  91. data/spec/notation/conversion/change_conversion_spec.rb +40 -0
  92. data/spec/notation/conversion/measure_note_map_spec.rb +73 -0
  93. data/spec/notation/conversion/measured_score_conversion_spec.rb +141 -0
  94. data/spec/notation/conversion/measured_score_converter_spec.rb +329 -0
  95. data/spec/notation/conversion/note_time_converter_spec.rb +81 -0
  96. data/spec/notation/conversion/tempo_conversion_spec.rb +40 -0
  97. data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +71 -0
  98. data/spec/notation/conversion/unmeasured_score_converter_spec.rb +116 -0
  99. data/spec/notation/model/change_spec.rb +90 -0
  100. data/spec/notation/model/link_spec.rb +83 -0
  101. data/spec/notation/model/meter_spec.rb +97 -0
  102. data/spec/notation/model/note_spec.rb +183 -0
  103. data/spec/notation/model/part_spec.rb +69 -0
  104. data/spec/notation/model/pitch_spec.rb +180 -0
  105. data/spec/notation/model/program_spec.rb +50 -0
  106. data/spec/notation/model/score_spec.rb +211 -0
  107. data/spec/notation/packing/change_packing_spec.rb +153 -0
  108. data/spec/notation/packing/part_packing_spec.rb +66 -0
  109. data/spec/notation/packing/program_packing_spec.rb +33 -0
  110. data/spec/notation/packing/score_packing_spec.rb +301 -0
  111. data/spec/notation/parsing/articulation_parsing_spec.rb +23 -0
  112. data/spec/notation/parsing/convenience_methods_spec.rb +99 -0
  113. data/spec/notation/parsing/duration_nodes_spec.rb +83 -0
  114. data/spec/notation/parsing/duration_parsing_spec.rb +70 -0
  115. data/spec/notation/parsing/link_nodes_spec.rb +30 -0
  116. data/spec/notation/parsing/link_parsing_spec.rb +13 -0
  117. data/spec/notation/parsing/meter_parsing_spec.rb +23 -0
  118. data/spec/notation/parsing/note_node_spec.rb +87 -0
  119. data/spec/notation/parsing/note_parsing_spec.rb +46 -0
  120. data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +28 -0
  121. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +11 -0
  122. data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +11 -0
  123. data/spec/notation/parsing/numbers/positive_float_spec.rb +28 -0
  124. data/spec/notation/parsing/numbers/positive_integer_spec.rb +28 -0
  125. data/spec/notation/parsing/numbers/positive_rational_spec.rb +28 -0
  126. data/spec/notation/parsing/pitch_node_spec.rb +38 -0
  127. data/spec/notation/parsing/pitch_parsing_spec.rb +14 -0
  128. data/spec/notation/parsing/segment_parsing_spec.rb +27 -0
  129. data/spec/notation/util/value_computer_spec.rb +146 -0
  130. data/spec/performance/conversion/glissando_converter_spec.rb +93 -0
  131. data/spec/performance/conversion/note_sequence_extractor_spec.rb +230 -0
  132. data/spec/performance/conversion/portamento_converter_spec.rb +91 -0
  133. data/spec/performance/conversion/score_collator_spec.rb +183 -0
  134. data/spec/performance/midi/midi_util_spec.rb +110 -0
  135. data/spec/performance/midi/part_sequencer_spec.rb +40 -0
  136. data/spec/performance/midi/score_sequencer_spec.rb +50 -0
  137. data/spec/performance/model/note_sequence_spec.rb +147 -0
  138. data/spec/performance/util/note_linker_spec.rb +68 -0
  139. data/spec/performance/util/optimization_spec.rb +73 -0
  140. data/spec/spec_helper.rb +43 -0
  141. metadata +323 -0
@@ -0,0 +1,116 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe UnmeasuredScoreConverter do
4
+ describe '#initialize' do
5
+ context 'current score is invalid' do
6
+ it 'should raise NotValidError' do
7
+ score = Score::Unmeasured.new(-1)
8
+ expect { UnmeasuredScoreConverter.new(score,200) }.to raise_error(NotValidError)
9
+ end
10
+ end
11
+ end
12
+
13
+ describe '#convert_parts' do
14
+ before :each do
15
+ @changeA = Change::Immediate.new(Dynamics::PP)
16
+ @changeB = Change::Gradual.new(Dynamics::F, 2)
17
+ @score = Score::Unmeasured.new(120,
18
+ parts: {
19
+ "normal" => Part.new(Dynamics::MP,
20
+ dynamic_changes: { 1 => @changeA, 3 => @changeB },
21
+ notes: "/4C2 /8D2 /8E2 /2C2".to_notes * 4),
22
+ "empty" => Part.new(Dynamics::PP)
23
+ }
24
+ )
25
+ @parts = UnmeasuredScoreConverter.new(@score,200).convert_parts
26
+ end
27
+
28
+ it 'should return Hash with original part names' do
29
+ @parts.should be_a Hash
30
+ @parts.keys.sort.should eq(@score.parts.keys.sort)
31
+ end
32
+
33
+ it 'should convert part dynamic change offsets from note-based to time-based' do
34
+ part = @parts["normal"]
35
+ part.dynamic_changes.keys.sort.should eq([2,6])
36
+ change = part.dynamic_changes[2.0]
37
+ change.value.should eq(@changeA.value)
38
+ change.duration.should eq(0)
39
+ change = part.dynamic_changes[6.0]
40
+ change.value.should eq(@changeB.value)
41
+ change.duration.should eq(4.0)
42
+ end
43
+
44
+ it 'should convert note durations to time durations' do
45
+ part = @parts["normal"]
46
+ part.notes.map {|x| x.duration }.should eq([0.5,0.25,0.25,1]*4)
47
+ end
48
+
49
+ context 'gradual changes with positive elapsed and/or remaining' do
50
+ it 'should change elapsed and remaining so they reflect time-based duration' do
51
+ score = Score::Unmeasured.new(120, parts: {
52
+ "abc" => Part.new(Dynamics::P, dynamic_changes: {
53
+ 2 => Change::Gradual.new(Dynamics::F,2,1,3),
54
+ 7 => Change::Gradual.new(Dynamics::F,1,4,5)
55
+ })
56
+ })
57
+ converter = UnmeasuredScoreConverter.new(score,200)
58
+ parts = converter.convert_parts
59
+ dcs = parts["abc"].dynamic_changes
60
+
61
+ dcs.keys.should eq([4,14])
62
+ dcs[4.0].should eq(Change::Gradual.new(Dynamics::F,4,2,6))
63
+ dcs[14.0].should eq(Change::Gradual.new(Dynamics::F,2,8,10))
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '#convert_program' do
69
+ before :each do
70
+ @prog = Program.new([0...4,2...5])
71
+ @score = Score::Unmeasured.new(120, program: @prog)
72
+ @converter = UnmeasuredScoreConverter.new(@score,200)
73
+ @prog2 = @converter.convert_program
74
+ end
75
+
76
+ it 'should return Program with same number of segments' do
77
+ @prog2.should be_a Program
78
+ @prog2.segments.size.should eq(@prog.segments.size)
79
+ end
80
+
81
+ it 'should convert program segments offsets from note-based to time-based' do
82
+ prog = @prog2
83
+ prog.segments[0].first.should eq(0)
84
+ prog.segments[0].last.should eq(8)
85
+ prog.segments[1].first.should eq(4)
86
+ prog.segments[1].last.should eq(10)
87
+ end
88
+ end
89
+
90
+ describe '#convert_score' do
91
+ it 'should return an timed score' do
92
+ score = Score::Unmeasured.new(120)
93
+ converter = UnmeasuredScoreConverter.new(score,200)
94
+ converter.convert_score.should be_a Score::Timed
95
+ end
96
+
97
+ it 'should use output from convert_program' do
98
+ prog = Program.new([0...4,2...5])
99
+ score = Score::Unmeasured.new(120, program: prog)
100
+ converter = UnmeasuredScoreConverter.new(score,200)
101
+ nscore = converter.convert_score
102
+ nscore.program.should eq(converter.convert_program)
103
+ end
104
+
105
+ it 'should use output from convert_parts' do
106
+ changeA = Change::Immediate.new(Dynamics::PP)
107
+ changeB = Change::Gradual.new(Dynamics::F, 2)
108
+ score = Score::Unmeasured.new(120,
109
+ parts: {"simple" => Part.new(Dynamics::MP, dynamic_changes: { 1 => changeA, 3 => changeB })}
110
+ )
111
+ converter = UnmeasuredScoreConverter.new(score,200)
112
+ nscore = converter.convert_score
113
+ nscore.parts.should eq(converter.convert_parts)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,90 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Change::Immediate do
4
+ context '#initialize' do
5
+ it 'should set value to given' do
6
+ Change::Immediate.new(5).value.should eq 5
7
+ end
8
+
9
+ it 'should set duration to 0' do
10
+ Change::Immediate.new(5).duration.should eq 0
11
+ end
12
+ end
13
+
14
+ describe '==' do
15
+ it 'should return true if two immediate changes have the same value' do
16
+ Change::Immediate.new(5).should eq(Change::Immediate.new(5))
17
+ end
18
+
19
+ it 'should return false if two immediate changes do not have the same value' do
20
+ Change::Immediate.new(5).should_not eq(Change::Immediate.new(4))
21
+ end
22
+ end
23
+
24
+ describe '#to_yaml' do
25
+ it 'should produce YAML that can be loaded' do
26
+ c = Change::Immediate.new(4)
27
+ YAML.load(c.to_yaml).should eq c
28
+ end
29
+ end
30
+ end
31
+
32
+ describe Change::Gradual do
33
+ context '.new' do
34
+ it 'should set value to given value' do
35
+ Change::Gradual.new(5,2).value.should eq 5
36
+ end
37
+
38
+ it 'should set duration to given impending duration' do
39
+ c = Change::Gradual.new(5,2)
40
+ c.duration.should eq 2
41
+ c.impending.should eq 2
42
+ end
43
+
44
+ it 'should set elapsed to 0 by default' do
45
+ Change::Gradual.new(5,2).elapsed.should eq 0
46
+ end
47
+
48
+ it 'should set remaining to 0 by default' do
49
+ Change::Gradual.new(5,2).remaining.should eq 0
50
+ end
51
+
52
+ it 'should compute total_duration to be elapsed + impending + remaining' do
53
+ Change::Gradual.new(100,7,2,3).total_duration.should eq(12)
54
+ end
55
+
56
+ it 'should raise NonPositiveError if impending is <= 0' do
57
+ expect { Change::Gradual.new(11,0) }.to raise_error(NonPositiveError)
58
+ expect { Change::Gradual.new(11,-1) }.to raise_error(NonPositiveError)
59
+ end
60
+
61
+ it 'should raise NegativeError if elapsed is < 0' do
62
+ expect { Change::Gradual.new(11,1,-1) }.to raise_error(NegativeError)
63
+ end
64
+
65
+ it 'should raise NegativeError if remaining is < 0' do
66
+ expect { Change::Gradual.new(11,1,0,-1) }.to raise_error(NegativeError)
67
+ end
68
+ end
69
+
70
+ describe '==' do
71
+ it 'should return true if two gradual changes have the same value and duration' do
72
+ Change::Gradual.new(5,2).should eq(Change::Gradual.new(5,2))
73
+ end
74
+
75
+ it 'should return false if two gradual changes do not have the same value' do
76
+ Change::Gradual.new(5,2).should_not eq(Change::Gradual.new(4,2))
77
+ end
78
+
79
+ it 'should return false if two gradual changes do not have the same duration' do
80
+ Change::Gradual.new(5,2).should_not eq(Change::Gradual.new(5,1))
81
+ end
82
+ end
83
+
84
+ describe '#to_yaml' do
85
+ it 'should produce YAML that can be loaded' do
86
+ c = Change::Gradual.new(4,2)
87
+ YAML.load(c.to_yaml).should eq c
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,83 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ {
4
+ Link::Glissando => Link::Portamento,
5
+ Link::Portamento => Link::Glissando,
6
+ Link::Slur => Link::Legato,
7
+ Link::Legato => Link::Slur
8
+ }.each do |klass,klass2|
9
+ describe klass do
10
+ describe '#initialize' do
11
+ it 'should assign the given pitch to :target_pitch' do
12
+ klass.new(C2).target_pitch.should eq(C2)
13
+ end
14
+ end
15
+
16
+ describe '#==' do
17
+ it 'should return true if two links have the same target pitch' do
18
+ klass.new(C2).should eq(klass.new(C2))
19
+ end
20
+
21
+ it 'should return false if two links do not have the same target pitch' do
22
+ klass.new(C2).should_not eq(klass.new(F5))
23
+ end
24
+
25
+ it 'should return false if the link type is different' do
26
+ klass.new(C2).should_not eq(klass2.new(C2))
27
+ end
28
+ end
29
+
30
+ describe '#clone' do
31
+ it 'should return a link equal to original' do
32
+ l = klass.new(C4)
33
+ l.clone.should eq l
34
+ end
35
+ end
36
+
37
+ describe '#to_yaml' do
38
+ it 'should produce YAML that can be loaded' do
39
+ l = klass.new(C5)
40
+ YAML.load(l.to_yaml).should eq l
41
+ end
42
+ end
43
+
44
+ describe '#to_s' do
45
+ it 'should produce string that include link char and target pitch str' do
46
+ l = klass.new(C3)
47
+ l.to_s.should eq(l.link_char + "C3")
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ describe Link::Tie do
54
+ describe '#==' do
55
+ it 'should return true if another Tie object is given' do
56
+ Link::Tie.new.should eq(Link::Tie.new)
57
+ end
58
+
59
+ it 'should return false if a non-Tie object is given' do
60
+ Link::Tie.new.should_not eq(Link::Portamento.new(C2))
61
+ end
62
+ end
63
+
64
+ describe '#clone' do
65
+ it 'should return a link equal to original' do
66
+ l = Link::Tie.new
67
+ l.clone.should eq l
68
+ end
69
+ end
70
+
71
+ describe '#to_yaml' do
72
+ it 'should produce YAML that can be loaded' do
73
+ l = Link::Tie.new
74
+ YAML.load(l.to_yaml).should eq l
75
+ end
76
+ end
77
+
78
+ describe '#to_s' do
79
+ it 'should return =' do
80
+ Link::Tie.new.to_s.should eq("=")
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Meter do
4
+ describe '#initialize' do
5
+ it 'should assign beats per measure and beat duration' do
6
+ [[4,"1/4".to_r],[3,"1/4".to_r],[6,"1/8".to_r]].each do |bpm,bd|
7
+ m = Meter.new(bpm,bd)
8
+ m.beats_per_measure.should eq bpm
9
+ m.beat_duration.should eq bd
10
+ end
11
+ end
12
+
13
+ it 'should derive measure duration' do
14
+ {
15
+ [4,"1/4".to_r] => "1/1".to_r,
16
+ [3,"1/4".to_r] => "3/4".to_r,
17
+ [6,"1/8".to_r] => "6/8".to_r,
18
+ [12,"1/8".to_r] => "12/8".to_r,
19
+ }.each do |bpm,bd|
20
+ m = Meter.new(bpm,bd)
21
+ m.measure_duration.should eq(bpm*bd)
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '#==' do
27
+ context 'meters with same beat duration and beats per measure' do
28
+ it 'should return true' do
29
+ m1 = Meter.new(4,"1/4".to_r)
30
+ m2 = Meter.new(4,"1/4".to_r)
31
+ m1.should eq m2
32
+ end
33
+ end
34
+
35
+ context 'meters with same meausre duration but different beat duration' do
36
+ it 'should return false' do
37
+ m1 = Meter.new(4,"1/4".to_r)
38
+ m2 = Meter.new(2,"1/2".to_r)
39
+ m1.should_not eq m2
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#to_yaml' do
45
+ it 'should produce YAML that can be loaded' do
46
+ m = Meter.new(4,"1/4".to_r)
47
+ YAML.load(m.to_yaml).should eq m
48
+ end
49
+ end
50
+
51
+ describe '#to_s' do
52
+ context 'beat duration with 1 in denominator' do
53
+ it 'should return string of fraction: beats_per_measure / beat_duration.denom' do
54
+ FOUR_FOUR.to_s.should eq("4/4")
55
+ TWO_FOUR.to_s.should eq("2/4")
56
+ THREE_FOUR.to_s.should eq("3/4")
57
+ TWO_TWO.to_s.should eq("2/2")
58
+ end
59
+ end
60
+
61
+ context 'beat duration with >1 in denominator' do
62
+ it 'should return beats_per_measure * beat_dur fraction' do
63
+ SIX_EIGHT.to_s.should eq("2*3/8")
64
+ Meter.new(3,"3/8".to_r).to_s.should eq("3*3/8")
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#valid?' do
70
+ {
71
+ '4/4 meter' => [4,'1/4'.to_r],
72
+ '2/4 meter' => [2,'1/4'.to_r],
73
+ '3/4 meter' => [2,'1/4'.to_r],
74
+ '6/8 meter' => [6,'1/8'.to_r],
75
+ '12/8 meter' => [12,'1/8'.to_r],
76
+ }.each do |context_str,args|
77
+ context context_str do
78
+ it 'should return true' do
79
+ Meter.new(*args).should be_valid
80
+ end
81
+ end
82
+ end
83
+
84
+ {
85
+ 'non-integer positive beats per measure' => [4.0,"1/4".to_r],
86
+ 'integer negative beats per measure' => [-1,"1/4".to_r],
87
+ 'zero beat duration' => [4,0.to_r],
88
+ 'negative beat duration' => [4,-1.to_r],
89
+ }.each do |context_str,args|
90
+ context context_str do
91
+ it 'should return false' do
92
+ Meter.new(*args).should be_invalid
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,183 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Note do
4
+ before :all do
5
+ @pitch = C4
6
+ end
7
+
8
+ describe '.new' do
9
+ it 'should assign :duration that is given during construction' do
10
+ Note.new(2).duration.should eq(2)
11
+ end
12
+
13
+ it "should assign :articulation to Note::DEFAULT_ARTICULATION if not given" do
14
+ Note.new(2).articulation.should eq(Note::DEFAULT_ARTICULATION)
15
+ end
16
+
17
+ it "should assign :articulation parameter if given during construction" do
18
+ Note.new(2, articulation: STACCATO).articulation.should eq(STACCATO)
19
+ end
20
+
21
+ it 'should assign :accented to false if not given' do
22
+ Note.new(2).accented.should be false
23
+ end
24
+
25
+ it 'should assign :accented if given' do
26
+ Note.new(2, accented: true).accented.should be true
27
+ end
28
+
29
+ it 'should have no pitches if not given' do
30
+ Note.new(2).pitches.should be_empty
31
+ end
32
+
33
+ it 'should assign pitches when given' do
34
+ pitches = [ C2, D2 ]
35
+ n = Note.new(2, pitches)
36
+ n.pitches.should include(pitches[0])
37
+ n.pitches.should include(pitches[1])
38
+ end
39
+ end
40
+
41
+ describe '#duration=' do
42
+ it 'should assign duration' do
43
+ note = Note.new 2, [@pitch]
44
+ note.duration = 3
45
+ note.duration.should eq 3
46
+ end
47
+ end
48
+
49
+ {
50
+ :sixteenth => Rational(1,16),
51
+ :dotted_SIXTEENTH => Rational(3,32),
52
+ :eighth => Rational(1,8),
53
+ :dotted_eighth => Rational(3,16),
54
+ :quarter => Rational(1,4),
55
+ :dotted_quarter => Rational(3,8),
56
+ :half => Rational(1,2),
57
+ :dotted_half => Rational(3,4),
58
+ :whole => Rational(1)
59
+ }.each do |fn_name,tgt_dur|
60
+ describe ".#{fn_name}" do
61
+ it "should make a note with duration #{tgt_dur}" do
62
+ Note.send(fn_name).duration.should eq tgt_dur
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '#transpose' do
68
+ context 'given pitch diff' do
69
+ before(:all) do
70
+ @note1 = Note::quarter([C2,F2], links:{C2=>Link::Glissando.new(D2)})
71
+ @interval = 4
72
+ @note2 = @note1.transpose(@interval)
73
+ end
74
+
75
+ it 'should modifiy pitches by adding pitch diff' do
76
+ @note2.pitches.each_with_index do |p,i|
77
+ p.diff(@note1.pitches[i]).should eq(@interval)
78
+ end
79
+ end
80
+
81
+ it 'should also affect link targets' do
82
+ @note1.links.each do |k,v|
83
+ kt = k.transpose(@interval)
84
+ @note2.links.should have_key kt
85
+ @note2.links[kt].target_pitch.should eq(v.target_pitch.transpose(@interval))
86
+ end
87
+ end
88
+ end
89
+
90
+ context 'with links that have no target pitch' do
91
+ it 'should not raise error' do
92
+ n = Note::half([E2],links: {E2 => Link::Tie.new})
93
+ expect { n.transpose(1) }.to_not raise_error
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#stretch' do
99
+ it 'should multiply note duration by ratio' do
100
+ note = Note::quarter.stretch(2)
101
+ note.duration.should eq(Rational(1,2))
102
+
103
+ note = Note::quarter.stretch(Rational(1,2))
104
+ note.duration.should eq(Rational(1,8))
105
+ end
106
+ end
107
+
108
+ describe '#to_s' do
109
+ before :all do
110
+ @note_parser = Parsing::NoteParser.new
111
+ end
112
+
113
+ context
114
+ it 'should produce string that when parsed produces an equal note' do
115
+ durations = ["1/8".to_r,"1".to_r,"5/3".to_r]
116
+ include Articulations
117
+ articulations = [NORMAL, SLUR, LEGATO, TENUTO, PORTATO, STACCATO, STACCATISSIMO ]
118
+ pitches_links_sets = [
119
+ [[],{}],
120
+ [[C2],{}],
121
+ [[A5,D6],{ A5 => Link::Tie.new }],
122
+ [[C5,E6,Gb2],{ C5 => Link::Glissando.new(D5) }],
123
+ [[A5,D6],{ A5 => Link::Legato.new(B5), D6 => Link::Slur.new(E6) }],
124
+ [[C5,E6,Gb2],{ C5 => Link::Portamento.new(D5), Gb2 => Link::Tie.new }],
125
+ ]
126
+
127
+ notes = []
128
+ durations.each do |d|
129
+ articulations.each do |art|
130
+ pitches_links_sets.each do |pitches_links_set|
131
+ pitches,links = pitches_links_set
132
+ [true,false].each do |acc|
133
+ notes.push Note.new(d, pitches, articulation: art, links: links, accented: acc)
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ notes.each do |note|
140
+ str = note.to_s
141
+ res = @note_parser.parse(str)
142
+ note2 = res.to_note
143
+ note2.should eq(note)
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#to_yaml' do
149
+ it 'should produce YAML that can be loaded' do
150
+ n = Note.new(1,[C2])
151
+ YAML.load(n.to_yaml).should eq n
152
+
153
+ n = Note.new(1,[C2,E2])
154
+ YAML.load(n.to_yaml).should eq n
155
+
156
+ n = Note.new(1,[C2], articulation: STACCATO)
157
+ YAML.load(n.to_yaml).should eq n
158
+
159
+ n = Note.new(1,[E2], links: {E2 => Link::Portamento.new(F2)})
160
+ YAML.load(n.to_yaml).should eq n
161
+ end
162
+ end
163
+
164
+ describe '#valid?' do
165
+ context 'note with positive duration' do
166
+ it 'should return true' do
167
+ Note.new(1,[C2]).should be_valid
168
+ end
169
+ end
170
+
171
+ context 'note with 0 duration' do
172
+ it 'should return false' do
173
+ Note.new(0,[C2]).should be_invalid
174
+ end
175
+ end
176
+
177
+ context 'note with negative duration' do
178
+ it 'should be invalid' do
179
+ Note.new(-1,[C2]).should be_invalid
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Part do
4
+ describe '#initialize' do
5
+ it 'should use empty containers for parameters not given' do
6
+ p = Part.new(Dynamics::MP)
7
+ p.notes.should be_empty
8
+ p.dynamic_changes.should be_empty
9
+ end
10
+
11
+ it "should assign parameters given during construction" do
12
+ p = Part.new(Dynamics::PPP)
13
+ p.start_dynamic.should eq Dynamics::PPP
14
+
15
+ notes = [Note::whole([A2]), Note::half]
16
+ dcs = { "1/2".to_r => Change::Immediate.new(Dynamics::P), 1 => Change::Gradual.new(Dynamics::MF,1) }
17
+ p = Part.new(Dynamics::FF, notes: notes, dynamic_changes: dcs)
18
+ p.notes.should eq notes
19
+ p.dynamic_changes.should eq dcs
20
+ end
21
+ end
22
+
23
+ describe '#to_yaml' do
24
+ it 'should produce YAML that can be loaded' do
25
+ p = Samples::SAMPLE_PART
26
+ YAML.load(p.to_yaml).should eq p
27
+ end
28
+ end
29
+
30
+ describe '#valid?' do
31
+ { 'negative start dynamic' => [-0.01],
32
+ 'start dynamic > 1' => [1.01],
33
+ #'dynamic change offsets outside 0..d' => [
34
+ # 0.5, :notes => [ Note::whole ],
35
+ # :dynamic_changes => { 1.2 => Change::Immediate.new(0.5) }],
36
+ #'dynamic change offsets outside 0..d' => [
37
+ # 0.5, :notes => [ Note::whole ],
38
+ # :dynamic_changes => { -0.2 => Change::Immediate.new(0.5) }],
39
+ 'dynamic change values outside 0..1' => [
40
+ 0.5, :notes => [ Note::whole ],
41
+ :dynamic_changes => { 0.2 => Change::Immediate.new(-0.01), 0.3 => Change::Gradual.new(1.01,0.2) }],
42
+ 'notes with 0 duration' => [ 0.5, :notes => [ Note.new(0) ]],
43
+ 'notes with negative duration' => [ 0.5, :notes => [ Note.new(-1) ]],
44
+ }.each do |context_str, args|
45
+ context context_str do
46
+ it 'should return false' do
47
+ Part.new(*args).should be_invalid
48
+ end
49
+ end
50
+ end
51
+
52
+ {
53
+ 'valid notes' => [ Dynamics::PP,
54
+ :notes => [ Note::whole, quarter([C5]) ]],
55
+ 'valid dynamic values' => [ Dynamics::MF,
56
+ :notes => [ Note::whole([C4]), Note::quarter ],
57
+ :dynamic_changes => {
58
+ 0.5 => Change::Immediate.new(Dynamics::MP),
59
+ 1.2 => Change::Gradual.new(Dynamics::FF, 0.05) } ],
60
+ }.each do |context_str, args|
61
+ context context_str do
62
+ it 'should return true' do
63
+ part = Part.new(*args)
64
+ part.should be_valid
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end