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