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,116 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe UnmeasuredScoreConverter do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'current score is invalid' do
|
6
|
+
it 'should raise NotValidError' do
|
7
|
+
score = Score::Unmeasured.new(-1)
|
8
|
+
expect { UnmeasuredScoreConverter.new(score,200) }.to raise_error(NotValidError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#convert_parts' do
|
14
|
+
before :each do
|
15
|
+
@changeA = Change::Immediate.new(Dynamics::PP)
|
16
|
+
@changeB = Change::Gradual.new(Dynamics::F, 2)
|
17
|
+
@score = Score::Unmeasured.new(120,
|
18
|
+
parts: {
|
19
|
+
"normal" => Part.new(Dynamics::MP,
|
20
|
+
dynamic_changes: { 1 => @changeA, 3 => @changeB },
|
21
|
+
notes: "/4C2 /8D2 /8E2 /2C2".to_notes * 4),
|
22
|
+
"empty" => Part.new(Dynamics::PP)
|
23
|
+
}
|
24
|
+
)
|
25
|
+
@parts = UnmeasuredScoreConverter.new(@score,200).convert_parts
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should return Hash with original part names' do
|
29
|
+
@parts.should be_a Hash
|
30
|
+
@parts.keys.sort.should eq(@score.parts.keys.sort)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should convert part dynamic change offsets from note-based to time-based' do
|
34
|
+
part = @parts["normal"]
|
35
|
+
part.dynamic_changes.keys.sort.should eq([2,6])
|
36
|
+
change = part.dynamic_changes[2.0]
|
37
|
+
change.value.should eq(@changeA.value)
|
38
|
+
change.duration.should eq(0)
|
39
|
+
change = part.dynamic_changes[6.0]
|
40
|
+
change.value.should eq(@changeB.value)
|
41
|
+
change.duration.should eq(4.0)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should convert note durations to time durations' do
|
45
|
+
part = @parts["normal"]
|
46
|
+
part.notes.map {|x| x.duration }.should eq([0.5,0.25,0.25,1]*4)
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'gradual changes with positive elapsed and/or remaining' do
|
50
|
+
it 'should change elapsed and remaining so they reflect time-based duration' do
|
51
|
+
score = Score::Unmeasured.new(120, parts: {
|
52
|
+
"abc" => Part.new(Dynamics::P, dynamic_changes: {
|
53
|
+
2 => Change::Gradual.new(Dynamics::F,2,1,3),
|
54
|
+
7 => Change::Gradual.new(Dynamics::F,1,4,5)
|
55
|
+
})
|
56
|
+
})
|
57
|
+
converter = UnmeasuredScoreConverter.new(score,200)
|
58
|
+
parts = converter.convert_parts
|
59
|
+
dcs = parts["abc"].dynamic_changes
|
60
|
+
|
61
|
+
dcs.keys.should eq([4,14])
|
62
|
+
dcs[4.0].should eq(Change::Gradual.new(Dynamics::F,4,2,6))
|
63
|
+
dcs[14.0].should eq(Change::Gradual.new(Dynamics::F,2,8,10))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#convert_program' do
|
69
|
+
before :each do
|
70
|
+
@prog = Program.new([0...4,2...5])
|
71
|
+
@score = Score::Unmeasured.new(120, program: @prog)
|
72
|
+
@converter = UnmeasuredScoreConverter.new(@score,200)
|
73
|
+
@prog2 = @converter.convert_program
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should return Program with same number of segments' do
|
77
|
+
@prog2.should be_a Program
|
78
|
+
@prog2.segments.size.should eq(@prog.segments.size)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should convert program segments offsets from note-based to time-based' do
|
82
|
+
prog = @prog2
|
83
|
+
prog.segments[0].first.should eq(0)
|
84
|
+
prog.segments[0].last.should eq(8)
|
85
|
+
prog.segments[1].first.should eq(4)
|
86
|
+
prog.segments[1].last.should eq(10)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#convert_score' do
|
91
|
+
it 'should return an timed score' do
|
92
|
+
score = Score::Unmeasured.new(120)
|
93
|
+
converter = UnmeasuredScoreConverter.new(score,200)
|
94
|
+
converter.convert_score.should be_a Score::Timed
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should use output from convert_program' do
|
98
|
+
prog = Program.new([0...4,2...5])
|
99
|
+
score = Score::Unmeasured.new(120, program: prog)
|
100
|
+
converter = UnmeasuredScoreConverter.new(score,200)
|
101
|
+
nscore = converter.convert_score
|
102
|
+
nscore.program.should eq(converter.convert_program)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should use output from convert_parts' do
|
106
|
+
changeA = Change::Immediate.new(Dynamics::PP)
|
107
|
+
changeB = Change::Gradual.new(Dynamics::F, 2)
|
108
|
+
score = Score::Unmeasured.new(120,
|
109
|
+
parts: {"simple" => Part.new(Dynamics::MP, dynamic_changes: { 1 => changeA, 3 => changeB })}
|
110
|
+
)
|
111
|
+
converter = UnmeasuredScoreConverter.new(score,200)
|
112
|
+
nscore = converter.convert_score
|
113
|
+
nscore.parts.should eq(converter.convert_parts)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Change::Immediate do
|
4
|
+
context '#initialize' do
|
5
|
+
it 'should set value to given' do
|
6
|
+
Change::Immediate.new(5).value.should eq 5
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should set duration to 0' do
|
10
|
+
Change::Immediate.new(5).duration.should eq 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '==' do
|
15
|
+
it 'should return true if two immediate changes have the same value' do
|
16
|
+
Change::Immediate.new(5).should eq(Change::Immediate.new(5))
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return false if two immediate changes do not have the same value' do
|
20
|
+
Change::Immediate.new(5).should_not eq(Change::Immediate.new(4))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#to_yaml' do
|
25
|
+
it 'should produce YAML that can be loaded' do
|
26
|
+
c = Change::Immediate.new(4)
|
27
|
+
YAML.load(c.to_yaml).should eq c
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe Change::Gradual do
|
33
|
+
context '.new' do
|
34
|
+
it 'should set value to given value' do
|
35
|
+
Change::Gradual.new(5,2).value.should eq 5
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should set duration to given impending duration' do
|
39
|
+
c = Change::Gradual.new(5,2)
|
40
|
+
c.duration.should eq 2
|
41
|
+
c.impending.should eq 2
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should set elapsed to 0 by default' do
|
45
|
+
Change::Gradual.new(5,2).elapsed.should eq 0
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should set remaining to 0 by default' do
|
49
|
+
Change::Gradual.new(5,2).remaining.should eq 0
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should compute total_duration to be elapsed + impending + remaining' do
|
53
|
+
Change::Gradual.new(100,7,2,3).total_duration.should eq(12)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should raise NonPositiveError if impending is <= 0' do
|
57
|
+
expect { Change::Gradual.new(11,0) }.to raise_error(NonPositiveError)
|
58
|
+
expect { Change::Gradual.new(11,-1) }.to raise_error(NonPositiveError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should raise NegativeError if elapsed is < 0' do
|
62
|
+
expect { Change::Gradual.new(11,1,-1) }.to raise_error(NegativeError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should raise NegativeError if remaining is < 0' do
|
66
|
+
expect { Change::Gradual.new(11,1,0,-1) }.to raise_error(NegativeError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '==' do
|
71
|
+
it 'should return true if two gradual changes have the same value and duration' do
|
72
|
+
Change::Gradual.new(5,2).should eq(Change::Gradual.new(5,2))
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should return false if two gradual changes do not have the same value' do
|
76
|
+
Change::Gradual.new(5,2).should_not eq(Change::Gradual.new(4,2))
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should return false if two gradual changes do not have the same duration' do
|
80
|
+
Change::Gradual.new(5,2).should_not eq(Change::Gradual.new(5,1))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#to_yaml' do
|
85
|
+
it 'should produce YAML that can be loaded' do
|
86
|
+
c = Change::Gradual.new(4,2)
|
87
|
+
YAML.load(c.to_yaml).should eq c
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
{
|
4
|
+
Link::Glissando => Link::Portamento,
|
5
|
+
Link::Portamento => Link::Glissando,
|
6
|
+
Link::Slur => Link::Legato,
|
7
|
+
Link::Legato => Link::Slur
|
8
|
+
}.each do |klass,klass2|
|
9
|
+
describe klass do
|
10
|
+
describe '#initialize' do
|
11
|
+
it 'should assign the given pitch to :target_pitch' do
|
12
|
+
klass.new(C2).target_pitch.should eq(C2)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#==' do
|
17
|
+
it 'should return true if two links have the same target pitch' do
|
18
|
+
klass.new(C2).should eq(klass.new(C2))
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return false if two links do not have the same target pitch' do
|
22
|
+
klass.new(C2).should_not eq(klass.new(F5))
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should return false if the link type is different' do
|
26
|
+
klass.new(C2).should_not eq(klass2.new(C2))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#clone' do
|
31
|
+
it 'should return a link equal to original' do
|
32
|
+
l = klass.new(C4)
|
33
|
+
l.clone.should eq l
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#to_yaml' do
|
38
|
+
it 'should produce YAML that can be loaded' do
|
39
|
+
l = klass.new(C5)
|
40
|
+
YAML.load(l.to_yaml).should eq l
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#to_s' do
|
45
|
+
it 'should produce string that include link char and target pitch str' do
|
46
|
+
l = klass.new(C3)
|
47
|
+
l.to_s.should eq(l.link_char + "C3")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe Link::Tie do
|
54
|
+
describe '#==' do
|
55
|
+
it 'should return true if another Tie object is given' do
|
56
|
+
Link::Tie.new.should eq(Link::Tie.new)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should return false if a non-Tie object is given' do
|
60
|
+
Link::Tie.new.should_not eq(Link::Portamento.new(C2))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#clone' do
|
65
|
+
it 'should return a link equal to original' do
|
66
|
+
l = Link::Tie.new
|
67
|
+
l.clone.should eq l
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#to_yaml' do
|
72
|
+
it 'should produce YAML that can be loaded' do
|
73
|
+
l = Link::Tie.new
|
74
|
+
YAML.load(l.to_yaml).should eq l
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#to_s' do
|
79
|
+
it 'should return =' do
|
80
|
+
Link::Tie.new.to_s.should eq("=")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Meter do
|
4
|
+
describe '#initialize' do
|
5
|
+
it 'should assign beats per measure and beat duration' do
|
6
|
+
[[4,"1/4".to_r],[3,"1/4".to_r],[6,"1/8".to_r]].each do |bpm,bd|
|
7
|
+
m = Meter.new(bpm,bd)
|
8
|
+
m.beats_per_measure.should eq bpm
|
9
|
+
m.beat_duration.should eq bd
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should derive measure duration' do
|
14
|
+
{
|
15
|
+
[4,"1/4".to_r] => "1/1".to_r,
|
16
|
+
[3,"1/4".to_r] => "3/4".to_r,
|
17
|
+
[6,"1/8".to_r] => "6/8".to_r,
|
18
|
+
[12,"1/8".to_r] => "12/8".to_r,
|
19
|
+
}.each do |bpm,bd|
|
20
|
+
m = Meter.new(bpm,bd)
|
21
|
+
m.measure_duration.should eq(bpm*bd)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#==' do
|
27
|
+
context 'meters with same beat duration and beats per measure' do
|
28
|
+
it 'should return true' do
|
29
|
+
m1 = Meter.new(4,"1/4".to_r)
|
30
|
+
m2 = Meter.new(4,"1/4".to_r)
|
31
|
+
m1.should eq m2
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'meters with same meausre duration but different beat duration' do
|
36
|
+
it 'should return false' do
|
37
|
+
m1 = Meter.new(4,"1/4".to_r)
|
38
|
+
m2 = Meter.new(2,"1/2".to_r)
|
39
|
+
m1.should_not eq m2
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#to_yaml' do
|
45
|
+
it 'should produce YAML that can be loaded' do
|
46
|
+
m = Meter.new(4,"1/4".to_r)
|
47
|
+
YAML.load(m.to_yaml).should eq m
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#to_s' do
|
52
|
+
context 'beat duration with 1 in denominator' do
|
53
|
+
it 'should return string of fraction: beats_per_measure / beat_duration.denom' do
|
54
|
+
FOUR_FOUR.to_s.should eq("4/4")
|
55
|
+
TWO_FOUR.to_s.should eq("2/4")
|
56
|
+
THREE_FOUR.to_s.should eq("3/4")
|
57
|
+
TWO_TWO.to_s.should eq("2/2")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'beat duration with >1 in denominator' do
|
62
|
+
it 'should return beats_per_measure * beat_dur fraction' do
|
63
|
+
SIX_EIGHT.to_s.should eq("2*3/8")
|
64
|
+
Meter.new(3,"3/8".to_r).to_s.should eq("3*3/8")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#valid?' do
|
70
|
+
{
|
71
|
+
'4/4 meter' => [4,'1/4'.to_r],
|
72
|
+
'2/4 meter' => [2,'1/4'.to_r],
|
73
|
+
'3/4 meter' => [2,'1/4'.to_r],
|
74
|
+
'6/8 meter' => [6,'1/8'.to_r],
|
75
|
+
'12/8 meter' => [12,'1/8'.to_r],
|
76
|
+
}.each do |context_str,args|
|
77
|
+
context context_str do
|
78
|
+
it 'should return true' do
|
79
|
+
Meter.new(*args).should be_valid
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
{
|
85
|
+
'non-integer positive beats per measure' => [4.0,"1/4".to_r],
|
86
|
+
'integer negative beats per measure' => [-1,"1/4".to_r],
|
87
|
+
'zero beat duration' => [4,0.to_r],
|
88
|
+
'negative beat duration' => [4,-1.to_r],
|
89
|
+
}.each do |context_str,args|
|
90
|
+
context context_str do
|
91
|
+
it 'should return false' do
|
92
|
+
Meter.new(*args).should be_invalid
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Note do
|
4
|
+
before :all do
|
5
|
+
@pitch = C4
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.new' do
|
9
|
+
it 'should assign :duration that is given during construction' do
|
10
|
+
Note.new(2).duration.should eq(2)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should assign :articulation to Note::DEFAULT_ARTICULATION if not given" do
|
14
|
+
Note.new(2).articulation.should eq(Note::DEFAULT_ARTICULATION)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should assign :articulation parameter if given during construction" do
|
18
|
+
Note.new(2, articulation: STACCATO).articulation.should eq(STACCATO)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should assign :accented to false if not given' do
|
22
|
+
Note.new(2).accented.should be false
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should assign :accented if given' do
|
26
|
+
Note.new(2, accented: true).accented.should be true
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should have no pitches if not given' do
|
30
|
+
Note.new(2).pitches.should be_empty
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should assign pitches when given' do
|
34
|
+
pitches = [ C2, D2 ]
|
35
|
+
n = Note.new(2, pitches)
|
36
|
+
n.pitches.should include(pitches[0])
|
37
|
+
n.pitches.should include(pitches[1])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#duration=' do
|
42
|
+
it 'should assign duration' do
|
43
|
+
note = Note.new 2, [@pitch]
|
44
|
+
note.duration = 3
|
45
|
+
note.duration.should eq 3
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
{
|
50
|
+
:sixteenth => Rational(1,16),
|
51
|
+
:dotted_SIXTEENTH => Rational(3,32),
|
52
|
+
:eighth => Rational(1,8),
|
53
|
+
:dotted_eighth => Rational(3,16),
|
54
|
+
:quarter => Rational(1,4),
|
55
|
+
:dotted_quarter => Rational(3,8),
|
56
|
+
:half => Rational(1,2),
|
57
|
+
:dotted_half => Rational(3,4),
|
58
|
+
:whole => Rational(1)
|
59
|
+
}.each do |fn_name,tgt_dur|
|
60
|
+
describe ".#{fn_name}" do
|
61
|
+
it "should make a note with duration #{tgt_dur}" do
|
62
|
+
Note.send(fn_name).duration.should eq tgt_dur
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#transpose' do
|
68
|
+
context 'given pitch diff' do
|
69
|
+
before(:all) do
|
70
|
+
@note1 = Note::quarter([C2,F2], links:{C2=>Link::Glissando.new(D2)})
|
71
|
+
@interval = 4
|
72
|
+
@note2 = @note1.transpose(@interval)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should modifiy pitches by adding pitch diff' do
|
76
|
+
@note2.pitches.each_with_index do |p,i|
|
77
|
+
p.diff(@note1.pitches[i]).should eq(@interval)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should also affect link targets' do
|
82
|
+
@note1.links.each do |k,v|
|
83
|
+
kt = k.transpose(@interval)
|
84
|
+
@note2.links.should have_key kt
|
85
|
+
@note2.links[kt].target_pitch.should eq(v.target_pitch.transpose(@interval))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with links that have no target pitch' do
|
91
|
+
it 'should not raise error' do
|
92
|
+
n = Note::half([E2],links: {E2 => Link::Tie.new})
|
93
|
+
expect { n.transpose(1) }.to_not raise_error
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#stretch' do
|
99
|
+
it 'should multiply note duration by ratio' do
|
100
|
+
note = Note::quarter.stretch(2)
|
101
|
+
note.duration.should eq(Rational(1,2))
|
102
|
+
|
103
|
+
note = Note::quarter.stretch(Rational(1,2))
|
104
|
+
note.duration.should eq(Rational(1,8))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#to_s' do
|
109
|
+
before :all do
|
110
|
+
@note_parser = Parsing::NoteParser.new
|
111
|
+
end
|
112
|
+
|
113
|
+
context
|
114
|
+
it 'should produce string that when parsed produces an equal note' do
|
115
|
+
durations = ["1/8".to_r,"1".to_r,"5/3".to_r]
|
116
|
+
include Articulations
|
117
|
+
articulations = [NORMAL, SLUR, LEGATO, TENUTO, PORTATO, STACCATO, STACCATISSIMO ]
|
118
|
+
pitches_links_sets = [
|
119
|
+
[[],{}],
|
120
|
+
[[C2],{}],
|
121
|
+
[[A5,D6],{ A5 => Link::Tie.new }],
|
122
|
+
[[C5,E6,Gb2],{ C5 => Link::Glissando.new(D5) }],
|
123
|
+
[[A5,D6],{ A5 => Link::Legato.new(B5), D6 => Link::Slur.new(E6) }],
|
124
|
+
[[C5,E6,Gb2],{ C5 => Link::Portamento.new(D5), Gb2 => Link::Tie.new }],
|
125
|
+
]
|
126
|
+
|
127
|
+
notes = []
|
128
|
+
durations.each do |d|
|
129
|
+
articulations.each do |art|
|
130
|
+
pitches_links_sets.each do |pitches_links_set|
|
131
|
+
pitches,links = pitches_links_set
|
132
|
+
[true,false].each do |acc|
|
133
|
+
notes.push Note.new(d, pitches, articulation: art, links: links, accented: acc)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
notes.each do |note|
|
140
|
+
str = note.to_s
|
141
|
+
res = @note_parser.parse(str)
|
142
|
+
note2 = res.to_note
|
143
|
+
note2.should eq(note)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#to_yaml' do
|
149
|
+
it 'should produce YAML that can be loaded' do
|
150
|
+
n = Note.new(1,[C2])
|
151
|
+
YAML.load(n.to_yaml).should eq n
|
152
|
+
|
153
|
+
n = Note.new(1,[C2,E2])
|
154
|
+
YAML.load(n.to_yaml).should eq n
|
155
|
+
|
156
|
+
n = Note.new(1,[C2], articulation: STACCATO)
|
157
|
+
YAML.load(n.to_yaml).should eq n
|
158
|
+
|
159
|
+
n = Note.new(1,[E2], links: {E2 => Link::Portamento.new(F2)})
|
160
|
+
YAML.load(n.to_yaml).should eq n
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe '#valid?' do
|
165
|
+
context 'note with positive duration' do
|
166
|
+
it 'should return true' do
|
167
|
+
Note.new(1,[C2]).should be_valid
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'note with 0 duration' do
|
172
|
+
it 'should return false' do
|
173
|
+
Note.new(0,[C2]).should be_invalid
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'note with negative duration' do
|
178
|
+
it 'should be invalid' do
|
179
|
+
Note.new(-1,[C2]).should be_invalid
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Part do
|
4
|
+
describe '#initialize' do
|
5
|
+
it 'should use empty containers for parameters not given' do
|
6
|
+
p = Part.new(Dynamics::MP)
|
7
|
+
p.notes.should be_empty
|
8
|
+
p.dynamic_changes.should be_empty
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should assign parameters given during construction" do
|
12
|
+
p = Part.new(Dynamics::PPP)
|
13
|
+
p.start_dynamic.should eq Dynamics::PPP
|
14
|
+
|
15
|
+
notes = [Note::whole([A2]), Note::half]
|
16
|
+
dcs = { "1/2".to_r => Change::Immediate.new(Dynamics::P), 1 => Change::Gradual.new(Dynamics::MF,1) }
|
17
|
+
p = Part.new(Dynamics::FF, notes: notes, dynamic_changes: dcs)
|
18
|
+
p.notes.should eq notes
|
19
|
+
p.dynamic_changes.should eq dcs
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#to_yaml' do
|
24
|
+
it 'should produce YAML that can be loaded' do
|
25
|
+
p = Samples::SAMPLE_PART
|
26
|
+
YAML.load(p.to_yaml).should eq p
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#valid?' do
|
31
|
+
{ 'negative start dynamic' => [-0.01],
|
32
|
+
'start dynamic > 1' => [1.01],
|
33
|
+
#'dynamic change offsets outside 0..d' => [
|
34
|
+
# 0.5, :notes => [ Note::whole ],
|
35
|
+
# :dynamic_changes => { 1.2 => Change::Immediate.new(0.5) }],
|
36
|
+
#'dynamic change offsets outside 0..d' => [
|
37
|
+
# 0.5, :notes => [ Note::whole ],
|
38
|
+
# :dynamic_changes => { -0.2 => Change::Immediate.new(0.5) }],
|
39
|
+
'dynamic change values outside 0..1' => [
|
40
|
+
0.5, :notes => [ Note::whole ],
|
41
|
+
:dynamic_changes => { 0.2 => Change::Immediate.new(-0.01), 0.3 => Change::Gradual.new(1.01,0.2) }],
|
42
|
+
'notes with 0 duration' => [ 0.5, :notes => [ Note.new(0) ]],
|
43
|
+
'notes with negative duration' => [ 0.5, :notes => [ Note.new(-1) ]],
|
44
|
+
}.each do |context_str, args|
|
45
|
+
context context_str do
|
46
|
+
it 'should return false' do
|
47
|
+
Part.new(*args).should be_invalid
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
{
|
53
|
+
'valid notes' => [ Dynamics::PP,
|
54
|
+
:notes => [ Note::whole, quarter([C5]) ]],
|
55
|
+
'valid dynamic values' => [ Dynamics::MF,
|
56
|
+
:notes => [ Note::whole([C4]), Note::quarter ],
|
57
|
+
:dynamic_changes => {
|
58
|
+
0.5 => Change::Immediate.new(Dynamics::MP),
|
59
|
+
1.2 => Change::Gradual.new(Dynamics::FF, 0.05) } ],
|
60
|
+
}.each do |context_str, args|
|
61
|
+
context context_str do
|
62
|
+
it 'should return true' do
|
63
|
+
part = Part.new(*args)
|
64
|
+
part.should be_valid
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|