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,180 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Pitch do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@cases =
|
7
|
+
[
|
8
|
+
{ :octave => 1, :semitone => 0, :cent => 0, :ratio => 2.0, :total_cents => 1200 },
|
9
|
+
{ :octave => 2, :semitone => 0, :cent => 0, :ratio => 4.0, :total_cents => 2400 },
|
10
|
+
{ :octave => 1, :semitone => 6, :cent => 0, :ratio => 2.828, :total_cents => 1800 },
|
11
|
+
{ :octave => 2, :semitone => 6, :cent => 0, :ratio => 5.657, :total_cents => 3000 },
|
12
|
+
{ :octave => 3, :semitone => 7, :cent => 0, :ratio => 11.986, :total_cents => 4300 },
|
13
|
+
{ :octave => -1, :semitone => 0, :cent => 0, :ratio => 0.5, :total_cents => -1200 },
|
14
|
+
{ :octave => -2, :semitone => 0, :cent => 0, :ratio => 0.25, :total_cents => -2400 },
|
15
|
+
{ :octave => -2, :semitone => 7, :cent => 0, :ratio => 0.37458, :total_cents => -1700 },
|
16
|
+
{ :octave => -1, :semitone => 9, :cent => 0, :ratio => 0.841, :total_cents => -300 },
|
17
|
+
{ :octave => 5, :semitone => 0, :cent => 20, :ratio => 32.372, :total_cents => 6020 },
|
18
|
+
{ :octave => 3, :semitone => 3, :cent => 95, :ratio => 10.0503, :total_cents => 3995 },
|
19
|
+
{ :octave => -3, :semitone => 2, :cent => -20, :ratio => 0.1387, :total_cents => -3420 },
|
20
|
+
{ :octave => -5, :semitone => -2, :cent => -77, :ratio => 0.02663, :total_cents => -6277 }
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be constructable with no parameters (no error raised)" do
|
25
|
+
lambda { Pitch.new }.should_not raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should take keyword args" do
|
29
|
+
obj = Pitch.new octave: 4, semitone: 3, cent: 5
|
30
|
+
obj.octave.should eq(4)
|
31
|
+
obj.semitone.should eq(3)
|
32
|
+
obj.cent.should eq(5)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should use default octave, semitone, and cent if none is given" do
|
36
|
+
p = Pitch.new
|
37
|
+
p.ratio.should be_within(0.01).of(1.0)
|
38
|
+
p.total_cents.should eq(0)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should use the octave and semitone given during construction" do
|
42
|
+
@cases.each do |case_data|
|
43
|
+
p = Pitch.new octave: case_data[:octave], semitone: case_data[:semitone], cent: case_data[:cent]
|
44
|
+
p.ratio.should be_within(0.01).of case_data[:ratio]
|
45
|
+
p.total_cents.should be case_data[:total_cents]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#diff' do
|
50
|
+
it 'should return the difference between the given pitch, in semitones' do
|
51
|
+
[
|
52
|
+
[C5,C4,12],
|
53
|
+
[C5,D5,-2],
|
54
|
+
[D5,C5,2],
|
55
|
+
[C5,Pitch.new(octave:5, cent:-5),5/100.0],
|
56
|
+
[A5,Pitch.new(octave:5, semitone: 5, cent: 22),378/100.0],
|
57
|
+
[A5,Pitch.new(octave:5, semitone: 11, cent: 85),-285/100.0],
|
58
|
+
].each do |a,b,c|
|
59
|
+
a.diff(b).should eq(c)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#transpose' do
|
65
|
+
it 'should add the given interval to total semitones' do
|
66
|
+
[0,1,2,5,12,13,-1,-2,-5,-12,-13].each do |interval|
|
67
|
+
pitch = Eb3.transpose(interval)
|
68
|
+
pitch.diff(Eb3).should eq(interval)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '.total_semitones' do
|
74
|
+
it 'should convert to whole/fractional total semitones value' do
|
75
|
+
C4.total_semitones.should eq(48)
|
76
|
+
C5.total_semitones.should eq(60)
|
77
|
+
C4.transpose(0.1).total_semitones.should eq(48.1)
|
78
|
+
C5.transpose(0.19).total_semitones.should eq(60.19)
|
79
|
+
C5.transpose(-0.19).total_semitones.should eq(59.81)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.from_semitones' do
|
84
|
+
it 'should convert (rounded) fractional part to cent value' do
|
85
|
+
Pitch.from_semitones(4).total_cents.should eq(400)
|
86
|
+
Pitch.from_semitones(4.11).total_cents.should eq(411)
|
87
|
+
Pitch.from_semitones(57.123).total_cents.should eq(5712)
|
88
|
+
Pitch.from_semitones(57.125).total_cents.should eq(5713)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '.from_ratio' do
|
93
|
+
it 'should return a Pitch with given ratio' do
|
94
|
+
@cases.each do |case_data|
|
95
|
+
p = Pitch.from_ratio case_data[:ratio]
|
96
|
+
p.total_cents.should eq case_data[:total_cents]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '.from_freq' do
|
102
|
+
it 'should make a pitch whose freq is approximately the given freq' do
|
103
|
+
[16.35, 440.0, 987.77].each do |given_freq|
|
104
|
+
pitch = Pitch.from_freq given_freq
|
105
|
+
pitch.freq.should be_within(0.01).of(given_freq)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should be comparable to other pitches" do
|
111
|
+
p1 = Pitch.new semitone: 1
|
112
|
+
p2 = Pitch.new semitone: 2
|
113
|
+
p3 = Pitch.new semitone: 3
|
114
|
+
|
115
|
+
p1.should eq(Pitch.new semitone: 1)
|
116
|
+
p2.should eq(Pitch.new semitone: 2)
|
117
|
+
p3.should eq(Pitch.new semitone: 3)
|
118
|
+
|
119
|
+
p1.should be < p2
|
120
|
+
p1.should be < p3
|
121
|
+
p2.should be < p3
|
122
|
+
p3.should be > p2
|
123
|
+
p3.should be > p1
|
124
|
+
p2.should be > p1
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should have freq of 440 for A4" do
|
128
|
+
a4 = Pitch.new octave: 4, semitone: 9
|
129
|
+
a4.freq.should be_within(0.01).of(440.0)
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#to_yaml' do
|
133
|
+
it 'should produce YAML that can be loaded' do
|
134
|
+
p = Pitch.new(octave: 1, semitone: 2)
|
135
|
+
YAML.load(p.to_yaml).should eq p
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '#to_s' do
|
140
|
+
context 'on-letter semitones' do
|
141
|
+
it 'should return the semitone letter + octave number' do
|
142
|
+
{ C0 => "C0", D1 => "D1", E7 => "E7",
|
143
|
+
F8 => "F8", G3 => "G3", A4 => "A4",
|
144
|
+
B5 => "B5", C2 => "C2" }.each do |p,s|
|
145
|
+
p.to_s.should eq s
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'off-letter semitones' do
|
151
|
+
context 'sharpit set false' do
|
152
|
+
it 'should return semitone letter + "b" + octave number' do
|
153
|
+
{ Db0 => "Db0", Eb1 => "Eb1", Gb7 => "Gb7",
|
154
|
+
Ab4 => "Ab4", Bb1 => "Bb1" }.each do |p,s|
|
155
|
+
p.to_s(false).should eq s
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'sharpit set true' do
|
161
|
+
it 'should return semitone letter + "#" + octave number' do
|
162
|
+
{ Db0 => "C#0", Eb1 => "D#1", Gb7 => "F#7",
|
163
|
+
Ab4 => "G#4", Bb1 => "A#1" }.each do |p,s|
|
164
|
+
p.to_s(true).should eq s
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'non-zero cent value' do
|
171
|
+
it 'should append +n (n = cent value)' do
|
172
|
+
{ C0.transpose(0.01) => "C0+1", E1.transpose(0.15) => "E1+15",
|
173
|
+
G5.transpose(-0.55) => "Gb5+45"
|
174
|
+
}.each do |p,s|
|
175
|
+
p.to_s.should eq s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Program do
|
4
|
+
|
5
|
+
it "should assign the segments given during initialization" do
|
6
|
+
segments = [ 0.0...5.0, 0.0...4.0, 5.0...10.0 ]
|
7
|
+
program = Program.new segments
|
8
|
+
program.segments.should eq(segments.clone)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#include?" do
|
12
|
+
it "should return true for any offset which would be encountered" do
|
13
|
+
segments = [ 0.0...5.0, 0.0...4.0, 5.0...10.0 ]
|
14
|
+
program = Program.new segments
|
15
|
+
|
16
|
+
[0.0, 4.0, 5.0, 9.999].each do |offset|
|
17
|
+
program.include?(offset).should be true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return false for any offset which would not be encountered" do
|
22
|
+
segments = [ 0.0...5.0, 0.0...4.0, 5.0...10.0 ]
|
23
|
+
program = Program.new segments
|
24
|
+
|
25
|
+
[-0.000001, 10.000001].each do |offset|
|
26
|
+
program.include?(offset).should be false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#valid?' do
|
32
|
+
context 'increasing, positive segments' do
|
33
|
+
it 'should return true' do
|
34
|
+
Program.new([0..2,1..2,0..4]).should be_valid
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'decreasing, positive segments' do
|
39
|
+
it 'should return false' do
|
40
|
+
Program.new([2..0,2..1,04..0]).should be_invalid
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'increasing, negative segments' do
|
45
|
+
it 'should return false' do
|
46
|
+
Program.new([-1..2,-2..0,-2..2]).should be_invalid
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Score do
|
4
|
+
describe '#collated?' do
|
5
|
+
context 'has program with more than one segment' do
|
6
|
+
it 'should return false' do
|
7
|
+
score = Score.new(program: Program.new([0..2,0..2]))
|
8
|
+
score.collated?.should be false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'has program with 0 segments' do
|
13
|
+
it 'should return false' do
|
14
|
+
score = Score.new(program: Program.new([]))
|
15
|
+
score.collated?.should be false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'has program with 1 segment' do
|
20
|
+
context 'program segment starts at 0' do
|
21
|
+
it 'should return true' do
|
22
|
+
score = Score.new(program: Program.new([0..2]))
|
23
|
+
score.collated?.should be true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'program segment does not start at 0' do
|
28
|
+
it 'should return false' do
|
29
|
+
score = Score.new(program: Program.new([1..2]))
|
30
|
+
score.collated?.should be false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe Score::Measured do
|
38
|
+
describe '#initialize' do
|
39
|
+
it 'should use empty containers for parameters not given' do
|
40
|
+
s = Score::Measured.new(FOUR_FOUR,120)
|
41
|
+
s.parts.should be_empty
|
42
|
+
s.program.segments.should be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should assign given parameters' do
|
46
|
+
m = FOUR_FOUR
|
47
|
+
s = Score::Measured.new(m,120)
|
48
|
+
s.start_meter.should eq m
|
49
|
+
s.start_tempo.should eq 120
|
50
|
+
|
51
|
+
parts = { "piano (LH)" => Samples::SAMPLE_PART }
|
52
|
+
program = Program.new [0...0.75, 0...0.75]
|
53
|
+
mcs = { 1 => Change::Immediate.new(THREE_FOUR) }
|
54
|
+
tcs = { 1 => Change::Immediate.new(100) }
|
55
|
+
|
56
|
+
s = Score::Measured.new(m,120,
|
57
|
+
parts: parts,
|
58
|
+
program: program,
|
59
|
+
meter_changes: mcs,
|
60
|
+
tempo_changes: tcs
|
61
|
+
)
|
62
|
+
s.parts.should eq parts
|
63
|
+
s.program.should eq program
|
64
|
+
s.meter_changes.should eq mcs
|
65
|
+
s.tempo_changes.should eq tcs
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#valid?' do
|
70
|
+
{
|
71
|
+
'valid start tempo' => [ FOUR_FOUR, 40 ],
|
72
|
+
'valid tempo changes' => [ FOUR_FOUR, 30,
|
73
|
+
:tempo_changes => { 1 => Change::Gradual.new(40, 2), 2 => Change::Immediate.new(50) } ],
|
74
|
+
'valid meter changes' => [ FOUR_FOUR, 120,
|
75
|
+
:meter_changes => { 1 => Change::Immediate.new(TWO_FOUR) } ],
|
76
|
+
'valid part' => [ FOUR_FOUR, 120, :parts => { "piano" => Samples::SAMPLE_PART }],
|
77
|
+
'valid program' => [ FOUR_FOUR, 120, :program => Program.new([0..2,0..2]) ]
|
78
|
+
}.each do |context_str,args|
|
79
|
+
context context_str do
|
80
|
+
it 'should return true' do
|
81
|
+
Score::Measured.new(*args).should be_valid
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
{
|
87
|
+
'start tempo object is negative' => [ FOUR_FOUR, -1],
|
88
|
+
'start tempo object is zero' => [ FOUR_FOUR, 0],
|
89
|
+
'invalid start meter' => [ Meter.new(-1,"1/4".to_r), 120],
|
90
|
+
'non-meter start meter' => [ 1, 120],
|
91
|
+
'invalid meter in change' => [ FOUR_FOUR, 120,
|
92
|
+
:meter_changes => { 1 => Change::Immediate.new(Meter.new(-2,"1/4".to_r)) } ],
|
93
|
+
'non-meter values in meter changes' => [ FOUR_FOUR, 120,
|
94
|
+
:meter_changes => { 1 => Change::Immediate.new(5) } ],
|
95
|
+
'non-immediate meter change' => [ FOUR_FOUR, 120,
|
96
|
+
:meter_changes => { 1 => Change::Gradual.new(TWO_FOUR,1) } ],
|
97
|
+
'non-integer meter change offset' => [ FOUR_FOUR, 120,
|
98
|
+
:meter_changes => { 1.1 => Change::Immediate.new(TWO_FOUR) } ],
|
99
|
+
'invalid part' => [ FOUR_FOUR, 120, :parts => { "piano" => Part.new(-0.1) }],
|
100
|
+
'invalid program' => [ FOUR_FOUR, 120, :program => Program.new([2..0]) ],
|
101
|
+
}.each do |context_str,args|
|
102
|
+
context context_str do
|
103
|
+
it 'should return false' do
|
104
|
+
Score::Measured.new(*args).should be_invalid
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe Score::Unmeasured do
|
112
|
+
describe '#initialize' do
|
113
|
+
it 'should use empty containers for parameters not given' do
|
114
|
+
s = Score::Unmeasured.new(30)
|
115
|
+
s.parts.should be_empty
|
116
|
+
s.program.segments.should be_empty
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should assign given parameters' do
|
120
|
+
s = Score::Unmeasured.new(30)
|
121
|
+
s.start_tempo.should eq(30)
|
122
|
+
|
123
|
+
parts = { "piano (LH)" => Samples::SAMPLE_PART }
|
124
|
+
program = Program.new [0...0.75, 0...0.75]
|
125
|
+
tcs = { 1 => Change::Immediate.new(40) }
|
126
|
+
|
127
|
+
s = Score::Unmeasured.new(30,
|
128
|
+
parts: parts,
|
129
|
+
program: program,
|
130
|
+
tempo_changes: tcs
|
131
|
+
)
|
132
|
+
s.parts.should eq parts
|
133
|
+
s.program.should eq program
|
134
|
+
s.tempo_changes.should eq tcs
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#valid?' do
|
139
|
+
{
|
140
|
+
'valid start tempo' => [ 40 ],
|
141
|
+
'valid tempo changes' => [ 30,
|
142
|
+
:tempo_changes => { 1 => Change::Gradual.new(40, 2), 2 => Change::Immediate.new(50) } ],
|
143
|
+
'valid part' => [ 30, :parts => { "piano" => Samples::SAMPLE_PART }],
|
144
|
+
'valid program' => [ 30, :program => Program.new([0..2,0..2]) ]
|
145
|
+
}.each do |context_str,args|
|
146
|
+
context context_str do
|
147
|
+
it 'should return true' do
|
148
|
+
Score::Unmeasured.new(*args).should be_valid
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
{
|
154
|
+
'start tempo valid is zero' => [ 0 ],
|
155
|
+
'start tempo valid is negative' => [ -1 ],
|
156
|
+
'tempo change value is not a valid value' => [ 30,
|
157
|
+
:tempo_changes => { 1 => Change::Gradual.new(-1,1) } ],
|
158
|
+
'invalid part' => [ 30, :parts => { "piano" => Part.new(-0.1) }],
|
159
|
+
'invalid program' => [ 30, :program => Program.new([2..0]) ],
|
160
|
+
}.each do |context_str,args|
|
161
|
+
context context_str do
|
162
|
+
it 'should return false' do
|
163
|
+
Score::Unmeasured.new(*args).should be_invalid
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe Score::Timed do
|
171
|
+
describe '#initialize' do
|
172
|
+
it 'should use empty containers for parameters not given' do
|
173
|
+
s = Score::Timed.new
|
174
|
+
s.parts.should be_empty
|
175
|
+
s.program.segments.should be_empty
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should assign given parameters' do
|
179
|
+
parts = { "piano (LH)" => Samples::SAMPLE_PART }
|
180
|
+
program = Program.new [0...0.75, 0...0.75]
|
181
|
+
|
182
|
+
s = Score::Timed.new(parts: parts, program: program)
|
183
|
+
s.parts.should eq parts
|
184
|
+
s.program.should eq program
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe '#valid?' do
|
189
|
+
{
|
190
|
+
'valid part' => [ :parts => { "piano" => Samples::SAMPLE_PART }],
|
191
|
+
'valid program' => [ :program => Program.new([0..2,0..2]) ]
|
192
|
+
}.each do |context_str,args|
|
193
|
+
context context_str do
|
194
|
+
it 'should return true' do
|
195
|
+
Score::Timed.new(*args).should be_valid
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
{
|
201
|
+
'invalid part' => [ :parts => { "piano" => Part.new(-0.1) }],
|
202
|
+
'invalid program' => [ :program => Program.new([2..0]) ],
|
203
|
+
}.each do |context_str,args|
|
204
|
+
context context_str do
|
205
|
+
it 'should return false' do
|
206
|
+
Score::Timed.new(*args).should be_invalid
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Change::Immediate do
|
4
|
+
describe '#pack' do
|
5
|
+
before :all do
|
6
|
+
@c = Change::Immediate.new(0.5)
|
7
|
+
@p = @c.pack
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should return a Hash' do
|
11
|
+
@p.should be_a Hash
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have "type" and "value" keys' do
|
15
|
+
@p.should have_key("type")
|
16
|
+
@p.should have_key("value")
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should assign "Immediate" to "type" key' do
|
20
|
+
@p["type"].should eq("Immediate")
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should assign value to "value" key' do
|
24
|
+
@p["value"].should eq(@c.value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Change::Gradual do
|
30
|
+
describe '#pack' do
|
31
|
+
context 'in general' do
|
32
|
+
before :all do
|
33
|
+
@c = Change::Gradual.new(200,2.5,3.3,4.5)
|
34
|
+
@p = @c.pack
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should return a Hash' do
|
38
|
+
@p.should be_a Hash
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should have "type", "value", and "impending" keys' do
|
42
|
+
@p.should have_key("type")
|
43
|
+
@p.should have_key("value")
|
44
|
+
@p.should have_key("impending")
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should assign "Gradual" to "type" key' do
|
48
|
+
@p["type"].should eq("Gradual")
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should assign value to "value" key' do
|
52
|
+
@p["value"].should eq(@c.value)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should assign impending to "impending" key' do
|
56
|
+
@p["impending"].should eq(@c.impending)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'elapsed and remaining are 0' do
|
61
|
+
before :all do
|
62
|
+
@c = Change::Gradual.new(200,2.5)
|
63
|
+
@p = @c.pack
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should *only* have "type", "value", and "impending" keys' do
|
67
|
+
@p.keys.sort.should eq(["impending","type","value"])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'elapsed is not 0, but remaining is 0' do
|
72
|
+
before :all do
|
73
|
+
@c = Change::Gradual.new(200,2.5,1.1)
|
74
|
+
@p = @c.pack
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should *only* have "type", "value", "impending", and "elapsed" keys' do
|
78
|
+
@p.keys.sort.should eq(["elapsed","impending","type","value"])
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should assign elapsed to "elapsed" key' do
|
82
|
+
@p["elapsed"].should eq(@c.elapsed)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'elapsed and remaining are not 0' do
|
87
|
+
before :all do
|
88
|
+
@c = Change::Gradual.new(200,2.5,1.1,2.2)
|
89
|
+
@p = @c.pack
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should *only* have "type", "value", "impending", "elapsed", and "remaining" keys' do
|
93
|
+
@p.keys.sort.should eq(["elapsed","impending","remaining","type","value"])
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should assign remaining to "remaining" key' do
|
97
|
+
@p["remaining"].should eq(@c.remaining)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe Change do
|
104
|
+
describe '.unpack' do
|
105
|
+
context 'given a packed immediate change' do
|
106
|
+
before :all do
|
107
|
+
@c = Change::Immediate.new(0.5)
|
108
|
+
@a = @c.pack
|
109
|
+
@c2 = Change.unpack(@a)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should return a Change::Immediate' do
|
113
|
+
@c2.should be_a Change::Immediate
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should successfully unpack the change value' do
|
117
|
+
@c2.value.should eq @c.value
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should successfully unpack the change duration' do
|
121
|
+
@c2.duration.should eq @c.duration
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'given a packed gradual change' do
|
126
|
+
before :all do
|
127
|
+
@c = Change::Gradual.new(0.3,1.5,1.1,0.2)
|
128
|
+
@a = @c.pack
|
129
|
+
@c2 = Change.unpack(@a)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should return a Change::Gradual' do
|
133
|
+
@c2.should be_a Change::Gradual
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should successfully unpack the change value' do
|
137
|
+
@c2.value.should eq @c.value
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should successfully unpack the change impending (duration)' do
|
141
|
+
@c2.impending.should eq @c.impending
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should successfully unpack the change elapsed' do
|
145
|
+
@c2.elapsed.should eq @c.elapsed
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should successfully unpack the change remaining' do
|
149
|
+
@c2.remaining.should eq @c.remaining
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Part do
|
4
|
+
before :all do
|
5
|
+
@p = Part.new(
|
6
|
+
Dynamics::MP,
|
7
|
+
notes: Note.split_parse("/4Bb2 /8 /8F3= /2F3 /4Bb2 /8 /8F3= /2F3"),
|
8
|
+
dynamic_changes: {
|
9
|
+
1 => Change::Immediate.new(Dynamics::PP),
|
10
|
+
2 => Change::Gradual.new(Dynamics::FF, 2.0)
|
11
|
+
}
|
12
|
+
)
|
13
|
+
|
14
|
+
@h = @p.pack
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#pack' do
|
18
|
+
it 'should produce a hash' do
|
19
|
+
@h.should be_a Hash
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should return a hash with keys: "notes", "start_dynamic", and "dynamic_changes"' do
|
23
|
+
@h.keys.should include("notes")
|
24
|
+
@h.keys.should include("start_dynamic")
|
25
|
+
@h.keys.should include("dynamic_changes")
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should pack notes into a string' do
|
29
|
+
@h['notes'].should be_a String
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should pack start dynamic as plain numeric value' do
|
33
|
+
@h['start_dynamic'].should be_a Numeric
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should pack dynamic changes as whatver type Change#pack returns' do
|
37
|
+
@h['dynamic_changes'].each do |offset,packed_v|
|
38
|
+
change_v = @p.dynamic_changes[offset]
|
39
|
+
t = change_v.pack.class
|
40
|
+
packed_v.should be_a t
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '.unpack' do
|
46
|
+
before :all do
|
47
|
+
@p2 = Part.unpack @h
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should return a Part' do
|
51
|
+
@p2.should be_a Part
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should successfuly unpack the notes' do
|
55
|
+
@p2.notes.should eq @p.notes
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should successfuly unpack the start dynamic' do
|
59
|
+
@p2.start_dynamic.should eq @p.start_dynamic
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should successfuly unpack the dynamic changes' do
|
63
|
+
@p2.dynamic_changes.should eq @p.dynamic_changes
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Program do
|
4
|
+
before :all do
|
5
|
+
@p = Program.new([0...5,3.0...6.5,("1/2".to_r)...("3/2".to_r)])
|
6
|
+
@a = @p.pack
|
7
|
+
@p2 = Program.unpack(@a)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#pack' do
|
11
|
+
it 'should return an Array' do
|
12
|
+
@a.should be_a Array
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return an array with same size as # of segments' do
|
16
|
+
@a.size.should eq @p.segments.size
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return an array of strings' do
|
20
|
+
@a.each {|x| x.should be_a String }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#unpack' do
|
25
|
+
it 'should return a Program' do
|
26
|
+
@p2.should be_a Program
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should successfully unpack program segments' do
|
30
|
+
@p2.segments.should eq @p.segments
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|