musicality 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +65 -0
- data/bin/midify +78 -0
- data/examples/hip.rb +32 -0
- data/examples/missed_connection.rb +26 -0
- data/examples/song1.rb +33 -0
- data/examples/song2.rb +32 -0
- data/lib/musicality/errors.rb +9 -0
- data/lib/musicality/notation/conversion/change_conversion.rb +19 -0
- data/lib/musicality/notation/conversion/measure_note_map.rb +40 -0
- data/lib/musicality/notation/conversion/measured_score_conversion.rb +70 -0
- data/lib/musicality/notation/conversion/measured_score_converter.rb +95 -0
- data/lib/musicality/notation/conversion/note_time_converter.rb +68 -0
- data/lib/musicality/notation/conversion/tempo_conversion.rb +25 -0
- data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +47 -0
- data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +64 -0
- data/lib/musicality/notation/model/articulations.rb +13 -0
- data/lib/musicality/notation/model/change.rb +62 -0
- data/lib/musicality/notation/model/dynamics.rb +12 -0
- data/lib/musicality/notation/model/link.rb +73 -0
- data/lib/musicality/notation/model/meter.rb +54 -0
- data/lib/musicality/notation/model/meters.rb +9 -0
- data/lib/musicality/notation/model/note.rb +120 -0
- data/lib/musicality/notation/model/part.rb +54 -0
- data/lib/musicality/notation/model/pitch.rb +163 -0
- data/lib/musicality/notation/model/pitches.rb +21 -0
- data/lib/musicality/notation/model/program.rb +53 -0
- data/lib/musicality/notation/model/score.rb +132 -0
- data/lib/musicality/notation/packing/change_packing.rb +46 -0
- data/lib/musicality/notation/packing/part_packing.rb +31 -0
- data/lib/musicality/notation/packing/program_packing.rb +16 -0
- data/lib/musicality/notation/packing/score_packing.rb +108 -0
- data/lib/musicality/notation/parsing/articulation_parsing.rb +264 -0
- data/lib/musicality/notation/parsing/articulation_parsing.treetop +59 -0
- data/lib/musicality/notation/parsing/convenience_methods.rb +74 -0
- data/lib/musicality/notation/parsing/duration_nodes.rb +21 -0
- data/lib/musicality/notation/parsing/duration_parsing.rb +205 -0
- data/lib/musicality/notation/parsing/duration_parsing.treetop +25 -0
- data/lib/musicality/notation/parsing/link_nodes.rb +35 -0
- data/lib/musicality/notation/parsing/link_parsing.rb +270 -0
- data/lib/musicality/notation/parsing/link_parsing.treetop +33 -0
- data/lib/musicality/notation/parsing/meter_parsing.rb +190 -0
- data/lib/musicality/notation/parsing/meter_parsing.treetop +29 -0
- data/lib/musicality/notation/parsing/note_node.rb +40 -0
- data/lib/musicality/notation/parsing/note_parsing.rb +229 -0
- data/lib/musicality/notation/parsing/note_parsing.treetop +28 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +289 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.treetop +29 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +64 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +17 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +86 -0
- data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.treetop +20 -0
- data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +503 -0
- data/lib/musicality/notation/parsing/numbers/positive_float_parsing.treetop +33 -0
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +95 -0
- data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +19 -0
- data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +84 -0
- data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.treetop +19 -0
- data/lib/musicality/notation/parsing/parseable.rb +30 -0
- data/lib/musicality/notation/parsing/pitch_node.rb +23 -0
- data/lib/musicality/notation/parsing/pitch_parsing.rb +448 -0
- data/lib/musicality/notation/parsing/pitch_parsing.treetop +52 -0
- data/lib/musicality/notation/parsing/segment_parsing.rb +141 -0
- data/lib/musicality/notation/parsing/segment_parsing.treetop +23 -0
- data/lib/musicality/notation/util/interpolation.rb +16 -0
- data/lib/musicality/notation/util/piecewise_function.rb +122 -0
- data/lib/musicality/notation/util/value_computer.rb +170 -0
- data/lib/musicality/performance/conversion/glissando_converter.rb +34 -0
- data/lib/musicality/performance/conversion/note_sequence_extractor.rb +98 -0
- data/lib/musicality/performance/conversion/portamento_converter.rb +24 -0
- data/lib/musicality/performance/conversion/score_collator.rb +126 -0
- data/lib/musicality/performance/midi/midi_events.rb +34 -0
- data/lib/musicality/performance/midi/midi_util.rb +31 -0
- data/lib/musicality/performance/midi/part_sequencer.rb +123 -0
- data/lib/musicality/performance/midi/score_sequencer.rb +45 -0
- data/lib/musicality/performance/model/note_attacks.rb +19 -0
- data/lib/musicality/performance/model/note_sequence.rb +111 -0
- data/lib/musicality/performance/util/note_linker.rb +28 -0
- data/lib/musicality/performance/util/optimization.rb +31 -0
- data/lib/musicality/validatable.rb +38 -0
- data/lib/musicality/version.rb +3 -0
- data/lib/musicality.rb +81 -0
- data/musicality.gemspec +30 -0
- data/spec/musicality_spec.rb +7 -0
- data/spec/notation/conversion/change_conversion_spec.rb +40 -0
- data/spec/notation/conversion/measure_note_map_spec.rb +73 -0
- data/spec/notation/conversion/measured_score_conversion_spec.rb +141 -0
- data/spec/notation/conversion/measured_score_converter_spec.rb +329 -0
- data/spec/notation/conversion/note_time_converter_spec.rb +81 -0
- data/spec/notation/conversion/tempo_conversion_spec.rb +40 -0
- data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +71 -0
- data/spec/notation/conversion/unmeasured_score_converter_spec.rb +116 -0
- data/spec/notation/model/change_spec.rb +90 -0
- data/spec/notation/model/link_spec.rb +83 -0
- data/spec/notation/model/meter_spec.rb +97 -0
- data/spec/notation/model/note_spec.rb +183 -0
- data/spec/notation/model/part_spec.rb +69 -0
- data/spec/notation/model/pitch_spec.rb +180 -0
- data/spec/notation/model/program_spec.rb +50 -0
- data/spec/notation/model/score_spec.rb +211 -0
- data/spec/notation/packing/change_packing_spec.rb +153 -0
- data/spec/notation/packing/part_packing_spec.rb +66 -0
- data/spec/notation/packing/program_packing_spec.rb +33 -0
- data/spec/notation/packing/score_packing_spec.rb +301 -0
- data/spec/notation/parsing/articulation_parsing_spec.rb +23 -0
- data/spec/notation/parsing/convenience_methods_spec.rb +99 -0
- data/spec/notation/parsing/duration_nodes_spec.rb +83 -0
- data/spec/notation/parsing/duration_parsing_spec.rb +70 -0
- data/spec/notation/parsing/link_nodes_spec.rb +30 -0
- data/spec/notation/parsing/link_parsing_spec.rb +13 -0
- data/spec/notation/parsing/meter_parsing_spec.rb +23 -0
- data/spec/notation/parsing/note_node_spec.rb +87 -0
- data/spec/notation/parsing/note_parsing_spec.rb +46 -0
- data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +28 -0
- data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +11 -0
- data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +11 -0
- data/spec/notation/parsing/numbers/positive_float_spec.rb +28 -0
- data/spec/notation/parsing/numbers/positive_integer_spec.rb +28 -0
- data/spec/notation/parsing/numbers/positive_rational_spec.rb +28 -0
- data/spec/notation/parsing/pitch_node_spec.rb +38 -0
- data/spec/notation/parsing/pitch_parsing_spec.rb +14 -0
- data/spec/notation/parsing/segment_parsing_spec.rb +27 -0
- data/spec/notation/util/value_computer_spec.rb +146 -0
- data/spec/performance/conversion/glissando_converter_spec.rb +93 -0
- data/spec/performance/conversion/note_sequence_extractor_spec.rb +230 -0
- data/spec/performance/conversion/portamento_converter_spec.rb +91 -0
- data/spec/performance/conversion/score_collator_spec.rb +183 -0
- data/spec/performance/midi/midi_util_spec.rb +110 -0
- data/spec/performance/midi/part_sequencer_spec.rb +40 -0
- data/spec/performance/midi/score_sequencer_spec.rb +50 -0
- data/spec/performance/model/note_sequence_spec.rb +147 -0
- data/spec/performance/util/note_linker_spec.rb +68 -0
- data/spec/performance/util/optimization_spec.rb +73 -0
- data/spec/spec_helper.rb +43 -0
- 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
|