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