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,141 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Score::Measured do
4
+ before :all do
5
+ @parts = {
6
+ "piano" => Part.new(Dynamics::MP,
7
+ notes: [Note.quarter(C4), Note.eighth(F3), Note.whole(C4), Note.half(D4)]*12,
8
+ dynamic_changes: {
9
+ 1 => Change::Immediate.new(Dynamics::MF),
10
+ 5 => Change::Immediate.new(Dynamics::FF),
11
+ 6 => Change::Gradual.new(Dynamics::MF,2),
12
+ 14 => Change::Immediate.new(Dynamics::PP),
13
+ }
14
+ )
15
+ }
16
+ @prog = Program.new([0...3,4...7,1...20,17..."45/2".to_r])
17
+ tcs = {
18
+ 0 => Change::Immediate.new(120),
19
+ 4 => Change::Gradual.new(60,2),
20
+ 11 => Change::Immediate.new(110)
21
+ }
22
+ mcs = {
23
+ 1 => Change::Immediate.new(TWO_FOUR),
24
+ 3 => Change::Immediate.new(SIX_EIGHT)
25
+ }
26
+ @score = Score::Measured.new(THREE_FOUR, 120,
27
+ parts: @parts,
28
+ program: @prog,
29
+ tempo_changes: tcs,
30
+ meter_changes: mcs
31
+ )
32
+ end
33
+
34
+ describe '#measure_offsets' do
35
+ before(:all){ @moffs = @score.measure_offsets }
36
+
37
+ it 'should return an already-sorted array' do
38
+ @moffs.should eq @moffs.sort
39
+ end
40
+
41
+ it 'should start with offset from start tempo/meter/dynamic' do
42
+ @moffs[0].should eq(0)
43
+ end
44
+
45
+ it 'should include offsets from tempo changes' do
46
+ @score.tempo_changes.each do |moff,change|
47
+ @moffs.should include(moff)
48
+ @moffs.should include(moff + change.duration)
49
+ end
50
+ end
51
+
52
+ it 'should include offsets from meter changes' do
53
+ @score.meter_changes.keys.each {|moff| @moffs.should include(moff) }
54
+ end
55
+
56
+ it "should include offsets from each part's dynamic changes" do
57
+ @score.parts.values.each do |part|
58
+ part.dynamic_changes.each do |moff,change|
59
+ @moffs.should include(moff)
60
+ change.offsets(moff).each {|offset| @moffs.should include(offset) }
61
+ end
62
+ end
63
+ end
64
+
65
+ it 'should include offsets from program segments' do
66
+ @score.program.segments.each do |seg|
67
+ @moffs.should include(seg.first)
68
+ @moffs.should include(seg.last)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#measure_durations' do
74
+ before(:all){ @mdurs = @score.measure_durations }
75
+
76
+ it 'should return a Hash' do
77
+ @mdurs.should be_a Hash
78
+ end
79
+
80
+ context 'no meter change at offset 0' do
81
+ it 'should have size of meter_changes.size + 1' do
82
+ @mdurs.size.should eq(@score.meter_changes.size + 1)
83
+ end
84
+
85
+ it 'should begin with offset 0' do
86
+ @mdurs.keys.min.should eq(0)
87
+ end
88
+
89
+ it 'should map start meter to offset 0' do
90
+ @mdurs[0].should eq(@score.start_meter.measure_duration)
91
+ end
92
+ end
93
+
94
+ context 'meter change at offset 0' do
95
+ before :all do
96
+ @change = Change::Immediate.new(THREE_FOUR)
97
+ @score2 = Score::Measured.new(FOUR_FOUR, 120, meter_changes: { 0 => @change })
98
+ @mdurs2 = @score2.measure_durations
99
+ end
100
+
101
+ it 'should have same size as meter changes' do
102
+ @mdurs2.size.should eq(@score2.meter_changes.size)
103
+ end
104
+
105
+ it 'should begin with offset 0' do
106
+ @mdurs2.keys.min.should eq(0)
107
+ end
108
+
109
+ it 'should begin with meter change at offset 0, instead of start meter' do
110
+ @mdurs2[0].should eq(@change.value.measure_duration)
111
+ end
112
+ end
113
+
114
+ context 'no meter changes' do
115
+ before :all do
116
+ @score3 = Score::Measured.new(FOUR_FOUR, 120)
117
+ @mdurs3 = @score3.measure_durations
118
+ end
119
+
120
+ it 'should have size 1' do
121
+ @mdurs3.size.should eq(1)
122
+ end
123
+
124
+ it 'should begin with offset 0' do
125
+ @mdurs3.keys.min.should eq(0)
126
+ end
127
+
128
+ it 'should begin with start meter' do
129
+ @mdurs3[0].should eq(@score3.start_meter.measure_duration)
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#to_unmeasured' do
135
+ it 'should use MeasuredScoreConverter#convert_score' do
136
+ nscore1 = @score.to_unmeasured
137
+ nscore2 = MeasuredScoreConverter.new(@score).convert_score
138
+ nscore1.should eq(nscore2)
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,329 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe MeasuredScoreConverter do
4
+ describe '#initialize' do
5
+ context 'current score is invalid' do
6
+ it 'should raise NotValidError' do
7
+ score = Score::Measured.new(1, 120)
8
+ expect { MeasuredScoreConverter.new(score) }.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::Measured.new(FOUR_FOUR, 120,
18
+ parts: {"simple" => Part.new(Dynamics::MP, dynamic_changes: { 1 => @changeA, 3 => @changeB })}
19
+ )
20
+ end
21
+
22
+ it 'should return Hash with original part names' do
23
+ parts = MeasuredScoreConverter.new(@score).convert_parts
24
+ parts.should be_a Hash
25
+ parts.keys.sort.should eq(@score.parts.keys.sort)
26
+ end
27
+
28
+ it 'should convert part dynamic change offsets from measure-based to note-based' do
29
+ parts = MeasuredScoreConverter.new(@score).convert_parts
30
+ parts.should have_key("simple")
31
+ part = parts["simple"]
32
+ part.dynamic_changes.keys.sort.should eq([1,3])
33
+ change = part.dynamic_changes[Rational(1,1)]
34
+ change.value.should eq(@changeA.value)
35
+ change.duration.should eq(0)
36
+ change = part.dynamic_changes[Rational(3,1)]
37
+ change.value.should eq(@changeB.value)
38
+ change.duration.should eq(2)
39
+
40
+ @score.start_meter = THREE_FOUR
41
+ parts = MeasuredScoreConverter.new(@score).convert_parts
42
+ parts.should have_key("simple")
43
+ part = parts["simple"]
44
+ part.dynamic_changes.keys.sort.should eq([Rational(3,4),Rational(9,4)])
45
+ change = part.dynamic_changes[Rational(3,4)]
46
+ change.value.should eq(@changeA.value)
47
+ change.duration.should eq(0)
48
+ change = part.dynamic_changes[Rational(9,4)]
49
+ change.value.should eq(@changeB.value)
50
+ change.duration.should eq(1.5)
51
+ end
52
+
53
+ context 'gradual changes with positive elapsed and/or remaining' do
54
+ it 'should change elapsed and remaining so they reflect note-based offsets' do
55
+ score = Score::Measured.new(THREE_FOUR,120, parts: {
56
+ "abc" => Part.new(Dynamics::P, dynamic_changes: {
57
+ 2 => Change::Gradual.new(Dynamics::F,2,1,3),
58
+ 7 => Change::Gradual.new(Dynamics::F,1,4,5)
59
+ })
60
+ })
61
+ converter = MeasuredScoreConverter.new(score)
62
+ parts = converter.convert_parts
63
+ dcs = parts["abc"].dynamic_changes
64
+
65
+ dcs.keys.should eq([Rational(6,4), Rational(21,4)])
66
+ dcs[Rational(3,2)].should eq(Change::Gradual.new(Dynamics::F,Rational(6,4),Rational(3,4),Rational(9,4)))
67
+ dcs[Rational(21,4)].should eq(Change::Gradual.new(Dynamics::F,Rational(3,4),Rational(12,4),Rational(15,4)))
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#convert_program' do
73
+ before :each do
74
+ @prog = Program.new([0...4,2...5])
75
+ @score = Score::Measured.new(FOUR_FOUR, 120, program: @prog)
76
+ @converter = MeasuredScoreConverter.new(@score)
77
+ end
78
+
79
+ it 'shuld return Program with same number of segments' do
80
+ prog = @converter.convert_program
81
+ prog.should be_a Program
82
+ prog.segments.size.should eq(@score.program.segments.size)
83
+ end
84
+
85
+ it 'should convert program segments offsets from measure-based to note-based' do
86
+ prog = MeasuredScoreConverter.new(@score).convert_program
87
+ prog.segments.size.should eq(2)
88
+ prog.segments[0].first.should eq(0)
89
+ prog.segments[0].last.should eq(4)
90
+ prog.segments[1].first.should eq(2)
91
+ prog.segments[1].last.should eq(5)
92
+
93
+ @score.start_meter = THREE_FOUR
94
+ prog = MeasuredScoreConverter.new(@score).convert_program
95
+ prog.segments.size.should eq(2)
96
+ prog.segments[0].first.should eq(0)
97
+ prog.segments[0].last.should eq(3)
98
+ prog.segments[1].first.should eq(1.5)
99
+ prog.segments[1].last.should eq(3.75)
100
+ end
101
+ end
102
+
103
+ describe '#convert_start_tempo' do
104
+ it 'should return a converted tempo object, with same type as givn tempo class' do
105
+ score = Score::Measured.new(SIX_EIGHT, 120)
106
+ converter = MeasuredScoreConverter.new(score)
107
+ tempo = converter.convert_start_tempo
108
+ tempo.should eq(180)
109
+ end
110
+ end
111
+
112
+ describe '#convert_tempo_changes' do
113
+ context 'immediate tempo changes' do
114
+ before :all do
115
+ @score = Score::Measured.new(THREE_FOUR, 120,
116
+ tempo_changes: { 1 => Change::Immediate.new(100),
117
+ 2.5 => Change::Immediate.new(90)}
118
+ )
119
+ @tcs = MeasuredScoreConverter.new(@score).convert_tempo_changes
120
+ end
121
+
122
+ it 'should change offset from measure-based to note-based' do
123
+ @tcs.keys.sort.should eq([0.75, 1.875])
124
+ end
125
+
126
+ it 'should change tempo value using Tempo::BPM.to_qnpm' do
127
+ @tcs.entries.first[1].value.should eq(Tempo::BPM.to_qnpm(100,Rational(1,4)))
128
+ @tcs.entries.last[1].value.should eq(Tempo::BPM.to_qnpm(90,Rational(1,4)))
129
+ end
130
+ end
131
+
132
+ context 'gradual tempo changes' do
133
+ context 'no meter changes within tempo change duration' do
134
+ before :all do
135
+ @score = Score::Measured.new(THREE_FOUR, 120,
136
+ tempo_changes: { 2 => Change::Gradual.new(100,2) },
137
+ meter_changes: { 1 => Change::Immediate.new(TWO_FOUR),
138
+ 4 => Change::Immediate.new(SIX_EIGHT) }
139
+ )
140
+ @tcs = MeasuredScoreConverter.new(@score).convert_tempo_changes
141
+ end
142
+
143
+ it 'should change tempo change offset to note-based' do
144
+ @tcs.keys.should eq([Rational(5,4)])
145
+ end
146
+
147
+ it 'should convert the tempo change' do
148
+ @tcs[Rational(5,4)].value.should eq(Tempo::BPM.to_qnpm(100,Rational(1,4)))
149
+ end
150
+
151
+ it 'should convert change duration to note-based' do
152
+ @tcs[Rational(5,4)].duration.should eq(1)
153
+ end
154
+ end
155
+
156
+ context 'single meter change within tempo change duration' do
157
+ before :all do
158
+ @tc_moff, @mc_moff = 2, 4
159
+ @tc = Change::Gradual.new(100,4)
160
+ @score = Score::Measured.new(THREE_FOUR, 120,
161
+ tempo_changes: { @tc_moff => @tc },
162
+ meter_changes: { @mc_moff => Change::Immediate.new(SIX_EIGHT) }
163
+ )
164
+ @tcs = MeasuredScoreConverter.new(@score).convert_tempo_changes
165
+ @mnoff_map = @score.measure_note_map
166
+
167
+ mend = @tc_moff + @tc.impending + @tc.remaining
168
+ @ndur = @mnoff_map[mend] - @mnoff_map[mend - @tc.total_duration]
169
+ end
170
+
171
+ it 'should split the one gradual change into two, durations adding to original total' do
172
+ @tcs.size.should eq(2)
173
+ @tcs.values.each {|x| x.should be_a Change::Gradual }
174
+ end
175
+
176
+ it 'should make each with same total duration' do
177
+ @tcs.values.each {|x| x.total_duration.should eq(@ndur) }
178
+ end
179
+
180
+ it 'should make durations so they sum to make the total duration' do
181
+ @tcs.values.map {|x| x.duration }.inject(0,:+).should eq(@ndur)
182
+ end
183
+
184
+ it 'should start first split change where original change would start' do
185
+ @tcs.should have_key(@mnoff_map[@tc_moff])
186
+ end
187
+
188
+ it 'should stop first split, and start second split where inner meter change occurs' do
189
+ pc1_start_noff = @mnoff_map[@tc_moff]
190
+ pc1_end_noff = pc1_start_noff + @tcs[pc1_start_noff].duration
191
+
192
+ pc2_start_noff = @mnoff_map[@mc_moff]
193
+ @tcs.should have_key(pc2_start_noff)
194
+ pc1_end_noff.should eq(pc2_start_noff)
195
+ end
196
+
197
+ it 'should stop second split change where original change would end' do
198
+ pc2_start_noff = @mnoff_map[@mc_moff]
199
+ pc2_end_noff = pc2_start_noff + @tcs[pc2_start_noff].duration
200
+ pc2_end_noff.should eq(@mnoff_map[@tc_moff + @tc.duration])
201
+ end
202
+ end
203
+
204
+ context 'two meter changes within tempo change duration' do
205
+ before :all do
206
+ @tc_moff, @mc1_moff, @mc2_moff = 2, 4, 5
207
+ @tc = Change::Gradual.new(100,5)
208
+ @score = Score::Measured.new(THREE_FOUR, 120,
209
+ tempo_changes: { @tc_moff => @tc},
210
+ meter_changes: { @mc1_moff => Change::Immediate.new(SIX_EIGHT),
211
+ @mc2_moff => Change::Immediate.new(TWO_FOUR) }
212
+ )
213
+ @tcs = MeasuredScoreConverter.new(@score).convert_tempo_changes
214
+ @mnoff_map = @score.measure_note_map
215
+
216
+ mend = @tc_moff + @tc.impending + @tc.remaining
217
+ @ndur = @mnoff_map[mend] - @mnoff_map[mend - @tc.total_duration]
218
+ end
219
+
220
+ it 'should split the one gradual change into three' do
221
+ @tcs.size.should eq(3)
222
+ @tcs.values.each {|x| x.should be_a Change::Gradual }
223
+ end
224
+
225
+ it 'should make each with same total duration' do
226
+ @tcs.values.each {|x| x.total_duration.should eq(@ndur) }
227
+ end
228
+
229
+ it 'should make durations so they sum to make the total duration' do
230
+ @tcs.values.map {|x| x.duration }.inject(0,:+).should eq(@ndur)
231
+ end
232
+
233
+ it 'should start first split change where original change would start' do
234
+ @tcs.should have_key(@mnoff_map[@tc_moff])
235
+ end
236
+
237
+ it 'should stop first split, and start second split change where 1st meter change occurs' do
238
+ pc1_start_noff = @mnoff_map[@tc_moff]
239
+ pc1_end_noff = pc1_start_noff + @tcs[pc1_start_noff].duration
240
+
241
+ pc2_start_noff = @mnoff_map[@mc1_moff]
242
+ @tcs.should have_key(pc2_start_noff)
243
+ pc1_end_noff.should eq(pc2_start_noff)
244
+ end
245
+
246
+ it 'should stop second split, and start third split change where 2st meter change occurs' do
247
+ pc2_start_noff = @mnoff_map[@mc1_moff]
248
+ pc2_end_noff = pc2_start_noff + @tcs[pc2_start_noff].duration
249
+
250
+ pc3_start_noff = @mnoff_map[@mc2_moff]
251
+ @tcs.should have_key(pc3_start_noff)
252
+ pc2_end_noff.should eq(pc3_start_noff)
253
+ end
254
+
255
+ it 'should stop third split change where original change would end' do
256
+ pc3_start_noff = @mnoff_map[@mc2_moff]
257
+ pc3_end_noff = pc3_start_noff + @tcs[pc3_start_noff].duration
258
+ pc3_end_noff.should eq(@mnoff_map[@tc_moff + @tc.duration])
259
+ end
260
+ end
261
+ end
262
+
263
+ context 'gradual tempo changes with positive elapsed and/or remaining' do
264
+ context 'no meter change during tempo change' do
265
+ it 'should simply change elapsed and remaining so they reflect note-based offsets' do
266
+ score = Score::Measured.new(THREE_FOUR,120, tempo_changes: {
267
+ 3 => Change::Gradual.new(100,5,2,3)
268
+ })
269
+ converter = MeasuredScoreConverter.new(score)
270
+ tcs = converter.convert_tempo_changes
271
+
272
+ tcs.keys.should eq([Rational(9,4)])
273
+ tcs[Rational(9,4)].should eq(Change::Gradual.new(100,Rational(15,4),Rational(6,4),Rational(9,4)))
274
+ end
275
+ end
276
+
277
+ context 'meter changes during tempo change' do
278
+ it 'should split tempo change, converting and adjusting elapsed/remaining with each sub-change' do
279
+ score = Score::Measured.new(THREE_FOUR,120,
280
+ meter_changes: { 4 => Change::Immediate.new(SIX_EIGHT),
281
+ 6 => Change::Immediate.new(TWO_FOUR) },
282
+ tempo_changes: { 3 => Change::Gradual.new(100,5,2,3) }
283
+ )
284
+ converter = MeasuredScoreConverter.new(score)
285
+ tcs = converter.convert_tempo_changes
286
+
287
+ tcs.keys.should eq([Rational(9,4), Rational(12,4), Rational(18,4)])
288
+ tcs[Rational(9,4)].should eq(Change::Gradual.new(100,0.75,1.5,4))
289
+ tcs[Rational(12,4)].should eq(Change::Gradual.new(Tempo::BPM.to_qnpm(100,SIX_EIGHT.beat_duration),1.5,2.25,2.5))
290
+ tcs[Rational(18,4)].should eq(Change::Gradual.new(Tempo::BPM.to_qnpm(100,TWO_FOUR.beat_duration),1,3.75,1.5))
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ describe '#convert_score' do
297
+ it 'should return an unmeasured score' do
298
+ score = Score::Measured.new(FOUR_FOUR, 120)
299
+ converter = MeasuredScoreConverter.new(score)
300
+ converter.convert_score.should be_a Score::Unmeasured
301
+ end
302
+
303
+ it 'should use output from convert_start_tempo' do
304
+ score = Score::Measured.new(FOUR_FOUR, 120)
305
+ converter = MeasuredScoreConverter.new(score)
306
+ nscore = converter.convert_score
307
+ nscore.start_tempo.should eq(converter.convert_start_tempo)
308
+ end
309
+
310
+ it 'should use output from convert_program' do
311
+ prog = Program.new([0...4,2...5])
312
+ score = Score::Measured.new(FOUR_FOUR, 120, program: prog)
313
+ converter = MeasuredScoreConverter.new(score)
314
+ nscore = converter.convert_score
315
+ nscore.program.should eq(converter.convert_program)
316
+ end
317
+
318
+ it 'should use output from convert_parts' do
319
+ changeA = Change::Immediate.new(Dynamics::PP)
320
+ changeB = Change::Gradual.new(Dynamics::F, 2)
321
+ score = Score::Measured.new(FOUR_FOUR, 120,
322
+ parts: {"simple" => Part.new(Dynamics::MP, dynamic_changes: { 1 => changeA, 3 => changeB })}
323
+ )
324
+ converter = MeasuredScoreConverter.new(score)
325
+ nscore = converter.convert_score
326
+ nscore.parts.should eq(converter.convert_parts)
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe NoteTimeConverter do
4
+ describe '#notes_per_second_at' do
5
+ it 'should convert tempo using Tempo::QNPM.to_nps' do
6
+ tc = ValueComputer.new(
7
+ 120, 1 => Change::Gradual.new(100,1), 2 => Change::Gradual.new(150,1))
8
+ converter = NoteTimeConverter.new(tc,200)
9
+ (0..2).step(0.2).each do |offset|
10
+ nps = Tempo::QNPM.to_nps(tc.value_at(offset))
11
+ converter.notes_per_second_at(offset).should eq(nps)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe "#time_elapsed" do
17
+ context "constant tempo" do
18
+ before :each do
19
+ @tempo_computer = ValueComputer.new 120
20
+ sample_rate = 48
21
+ @converter = NoteTimeConverter.new(@tempo_computer, sample_rate)
22
+ end
23
+
24
+ it "should return a time of zero when note end is zero." do
25
+ @converter.time_elapsed(0, 0).should eq(0)
26
+ end
27
+
28
+ it "should return a time of 1 second when note end is equal to the initial notes-per-second" do
29
+ note_end = @converter.notes_per_second_at(0)
30
+ @converter.time_elapsed(0, note_end).should eq(1)
31
+ end
32
+ end
33
+
34
+ context "linear tempo-change" do
35
+ before :each do
36
+ @tempo_computer = ValueComputer.new(
37
+ 120, 1 => Change::Gradual.new(60, 1))
38
+ sample_rate = 200
39
+ @converter = NoteTimeConverter.new(@tempo_computer, sample_rate)
40
+ end
41
+
42
+ it "should return a time of zero when note end is zero." do
43
+ @converter.time_elapsed(0.0, 0.0).should eq(0.0)
44
+ end
45
+
46
+ it "should return a time of 3 sec during a 1-note long transition from 120bpm to 60bpm" do
47
+ @converter.notes_per_second_at(1.0).should eq(0.5)
48
+ @converter.notes_per_second_at(2.0).should eq(0.25)
49
+
50
+ @converter.time_elapsed(1.0, 2.0).should be_within(0.05).of(2.77)
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ describe "#map_note_offsets_to_time_offsets" do
57
+ context "constant tempo" do
58
+ before :each do
59
+ @tempo_computer = ValueComputer.new 120
60
+ sample_rate = 4800
61
+ @converter = NoteTimeConverter.new(
62
+ @tempo_computer, sample_rate)
63
+ end
64
+
65
+ it "should map offset 0.0 to time 0.0" do
66
+ map = @converter.note_time_map [0.0]
67
+ map[0.0].should eq(0.0)
68
+ end
69
+
70
+ it "should map offset 0.25 to time 0.5" do
71
+ map = @converter.note_time_map [0.0, 0.25]
72
+ map[0.25].should be_within(0.01).of(0.5)
73
+ end
74
+
75
+ it "should map offset 1.0 to time 2.0" do
76
+ map = @converter.note_time_map [0.0, 1.0]
77
+ map[1.0].should be_within(0.01).of(2.0)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Tempo::QNPM do
4
+ before :all do
5
+ @tempo = 60
6
+ end
7
+
8
+ describe '#to_nps' do
9
+ it 'should change tempo value to be 1/240th' do
10
+ Tempo::QNPM.to_nps(@tempo).should eq(Rational(1,4))
11
+ end
12
+ end
13
+
14
+ describe '#to_bpm' do
15
+ it 'should divide tempo value by (4*beatdur)' do
16
+ Tempo::QNPM.to_bpm(@tempo, Rational(1,4)).should eq(60)
17
+ Tempo::QNPM.to_bpm(@tempo, Rational(1,2)).should eq(30)
18
+ end
19
+ end
20
+ end
21
+
22
+ describe Tempo::BPM do
23
+ before :all do
24
+ @tempo = 60
25
+ end
26
+
27
+ describe '#to_nps' do
28
+ it 'should multiply tempo value by beatdur/60' do
29
+ Tempo::BPM.to_nps(@tempo,Rational(1,4)).should eq(Rational(1,4))
30
+ end
31
+ end
32
+
33
+ describe '#to_qnpm' do
34
+ it 'should multiply tempo value by (4*beatdur)' do
35
+ Tempo::BPM.to_qnpm(@tempo,Rational(1,8)).should eq(30)
36
+ Tempo::BPM.to_qnpm(@tempo,Rational(1,4)).should eq(60)
37
+ Tempo::BPM.to_qnpm(@tempo,Rational(1,2)).should eq(120)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Score::Unmeasured do
4
+ before :all do
5
+ @parts = {
6
+ "piano" => Part.new(Dynamics::MP,
7
+ notes: [Note.quarter(C4), Note.eighth(F3), Note.whole(C4), Note.half(D4)]*12,
8
+ dynamic_changes: {
9
+ 1 => Change::Immediate.new(Dynamics::MF),
10
+ 5 => Change::Immediate.new(Dynamics::FF),
11
+ 6 => Change::Gradual.new(Dynamics::MF,2),
12
+ 14 => Change::Immediate.new(Dynamics::PP),
13
+ }
14
+ )
15
+ }
16
+ @prog = Program.new([0...3,4...7,1...20,17..."45/2".to_r])
17
+ tcs = {
18
+ 0 => Change::Immediate.new(120),
19
+ 4 => Change::Gradual.new(60,2),
20
+ 11 => Change::Immediate.new(110)
21
+ }
22
+ @score = Score::Unmeasured.new(120,
23
+ parts: @parts,
24
+ program: @prog,
25
+ tempo_changes: tcs,
26
+ )
27
+ end
28
+
29
+ describe '#note_offsets' do
30
+ before(:all){ @noffs = @score.note_offsets }
31
+
32
+ it 'should return an already-sorted array' do
33
+ @noffs.should eq @noffs.sort
34
+ end
35
+
36
+ it 'should start with offset from start tempo/dynamic' do
37
+ @noffs[0].should eq(0)
38
+ end
39
+
40
+ it 'should include offsets from tempo changes' do
41
+ @score.tempo_changes.each do |noff,change|
42
+ @noffs.should include(noff)
43
+ @noffs.should include(noff + change.duration)
44
+ end
45
+ end
46
+
47
+ it "should include offsets from each part's dynamic changes" do
48
+ @score.parts.values.each do |part|
49
+ part.dynamic_changes.each do |noff,change|
50
+ @noffs.should include(noff)
51
+ change.offsets(noff).each {|offset| @noffs.should include(offset) }
52
+ end
53
+ end
54
+ end
55
+
56
+ it 'should include offsets from program segments' do
57
+ @score.program.segments.each do |seg|
58
+ @noffs.should include(seg.first)
59
+ @noffs.should include(seg.last)
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#to_timed' do
65
+ it 'should use UnmeasuredScoreConverter#convert_score' do
66
+ nscore1 = @score.to_timed(200)
67
+ nscore2 = UnmeasuredScoreConverter.new(@score,200).convert_score
68
+ nscore1.should eq(nscore2)
69
+ end
70
+ end
71
+ end