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