musicality 0.1.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 (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