musicality 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +47 -0
  8. data/Rakefile +65 -0
  9. data/bin/midify +78 -0
  10. data/examples/hip.rb +32 -0
  11. data/examples/missed_connection.rb +26 -0
  12. data/examples/song1.rb +33 -0
  13. data/examples/song2.rb +32 -0
  14. data/lib/musicality/errors.rb +9 -0
  15. data/lib/musicality/notation/conversion/change_conversion.rb +19 -0
  16. data/lib/musicality/notation/conversion/measure_note_map.rb +40 -0
  17. data/lib/musicality/notation/conversion/measured_score_conversion.rb +70 -0
  18. data/lib/musicality/notation/conversion/measured_score_converter.rb +95 -0
  19. data/lib/musicality/notation/conversion/note_time_converter.rb +68 -0
  20. data/lib/musicality/notation/conversion/tempo_conversion.rb +25 -0
  21. data/lib/musicality/notation/conversion/unmeasured_score_conversion.rb +47 -0
  22. data/lib/musicality/notation/conversion/unmeasured_score_converter.rb +64 -0
  23. data/lib/musicality/notation/model/articulations.rb +13 -0
  24. data/lib/musicality/notation/model/change.rb +62 -0
  25. data/lib/musicality/notation/model/dynamics.rb +12 -0
  26. data/lib/musicality/notation/model/link.rb +73 -0
  27. data/lib/musicality/notation/model/meter.rb +54 -0
  28. data/lib/musicality/notation/model/meters.rb +9 -0
  29. data/lib/musicality/notation/model/note.rb +120 -0
  30. data/lib/musicality/notation/model/part.rb +54 -0
  31. data/lib/musicality/notation/model/pitch.rb +163 -0
  32. data/lib/musicality/notation/model/pitches.rb +21 -0
  33. data/lib/musicality/notation/model/program.rb +53 -0
  34. data/lib/musicality/notation/model/score.rb +132 -0
  35. data/lib/musicality/notation/packing/change_packing.rb +46 -0
  36. data/lib/musicality/notation/packing/part_packing.rb +31 -0
  37. data/lib/musicality/notation/packing/program_packing.rb +16 -0
  38. data/lib/musicality/notation/packing/score_packing.rb +108 -0
  39. data/lib/musicality/notation/parsing/articulation_parsing.rb +264 -0
  40. data/lib/musicality/notation/parsing/articulation_parsing.treetop +59 -0
  41. data/lib/musicality/notation/parsing/convenience_methods.rb +74 -0
  42. data/lib/musicality/notation/parsing/duration_nodes.rb +21 -0
  43. data/lib/musicality/notation/parsing/duration_parsing.rb +205 -0
  44. data/lib/musicality/notation/parsing/duration_parsing.treetop +25 -0
  45. data/lib/musicality/notation/parsing/link_nodes.rb +35 -0
  46. data/lib/musicality/notation/parsing/link_parsing.rb +270 -0
  47. data/lib/musicality/notation/parsing/link_parsing.treetop +33 -0
  48. data/lib/musicality/notation/parsing/meter_parsing.rb +190 -0
  49. data/lib/musicality/notation/parsing/meter_parsing.treetop +29 -0
  50. data/lib/musicality/notation/parsing/note_node.rb +40 -0
  51. data/lib/musicality/notation/parsing/note_parsing.rb +229 -0
  52. data/lib/musicality/notation/parsing/note_parsing.treetop +28 -0
  53. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.rb +289 -0
  54. data/lib/musicality/notation/parsing/numbers/nonnegative_float_parsing.treetop +29 -0
  55. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.rb +64 -0
  56. data/lib/musicality/notation/parsing/numbers/nonnegative_integer_parsing.treetop +17 -0
  57. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.rb +86 -0
  58. data/lib/musicality/notation/parsing/numbers/nonnegative_rational_parsing.treetop +20 -0
  59. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.rb +503 -0
  60. data/lib/musicality/notation/parsing/numbers/positive_float_parsing.treetop +33 -0
  61. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.rb +95 -0
  62. data/lib/musicality/notation/parsing/numbers/positive_integer_parsing.treetop +19 -0
  63. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.rb +84 -0
  64. data/lib/musicality/notation/parsing/numbers/positive_rational_parsing.treetop +19 -0
  65. data/lib/musicality/notation/parsing/parseable.rb +30 -0
  66. data/lib/musicality/notation/parsing/pitch_node.rb +23 -0
  67. data/lib/musicality/notation/parsing/pitch_parsing.rb +448 -0
  68. data/lib/musicality/notation/parsing/pitch_parsing.treetop +52 -0
  69. data/lib/musicality/notation/parsing/segment_parsing.rb +141 -0
  70. data/lib/musicality/notation/parsing/segment_parsing.treetop +23 -0
  71. data/lib/musicality/notation/util/interpolation.rb +16 -0
  72. data/lib/musicality/notation/util/piecewise_function.rb +122 -0
  73. data/lib/musicality/notation/util/value_computer.rb +170 -0
  74. data/lib/musicality/performance/conversion/glissando_converter.rb +34 -0
  75. data/lib/musicality/performance/conversion/note_sequence_extractor.rb +98 -0
  76. data/lib/musicality/performance/conversion/portamento_converter.rb +24 -0
  77. data/lib/musicality/performance/conversion/score_collator.rb +126 -0
  78. data/lib/musicality/performance/midi/midi_events.rb +34 -0
  79. data/lib/musicality/performance/midi/midi_util.rb +31 -0
  80. data/lib/musicality/performance/midi/part_sequencer.rb +123 -0
  81. data/lib/musicality/performance/midi/score_sequencer.rb +45 -0
  82. data/lib/musicality/performance/model/note_attacks.rb +19 -0
  83. data/lib/musicality/performance/model/note_sequence.rb +111 -0
  84. data/lib/musicality/performance/util/note_linker.rb +28 -0
  85. data/lib/musicality/performance/util/optimization.rb +31 -0
  86. data/lib/musicality/validatable.rb +38 -0
  87. data/lib/musicality/version.rb +3 -0
  88. data/lib/musicality.rb +81 -0
  89. data/musicality.gemspec +30 -0
  90. data/spec/musicality_spec.rb +7 -0
  91. data/spec/notation/conversion/change_conversion_spec.rb +40 -0
  92. data/spec/notation/conversion/measure_note_map_spec.rb +73 -0
  93. data/spec/notation/conversion/measured_score_conversion_spec.rb +141 -0
  94. data/spec/notation/conversion/measured_score_converter_spec.rb +329 -0
  95. data/spec/notation/conversion/note_time_converter_spec.rb +81 -0
  96. data/spec/notation/conversion/tempo_conversion_spec.rb +40 -0
  97. data/spec/notation/conversion/unmeasured_score_conversion_spec.rb +71 -0
  98. data/spec/notation/conversion/unmeasured_score_converter_spec.rb +116 -0
  99. data/spec/notation/model/change_spec.rb +90 -0
  100. data/spec/notation/model/link_spec.rb +83 -0
  101. data/spec/notation/model/meter_spec.rb +97 -0
  102. data/spec/notation/model/note_spec.rb +183 -0
  103. data/spec/notation/model/part_spec.rb +69 -0
  104. data/spec/notation/model/pitch_spec.rb +180 -0
  105. data/spec/notation/model/program_spec.rb +50 -0
  106. data/spec/notation/model/score_spec.rb +211 -0
  107. data/spec/notation/packing/change_packing_spec.rb +153 -0
  108. data/spec/notation/packing/part_packing_spec.rb +66 -0
  109. data/spec/notation/packing/program_packing_spec.rb +33 -0
  110. data/spec/notation/packing/score_packing_spec.rb +301 -0
  111. data/spec/notation/parsing/articulation_parsing_spec.rb +23 -0
  112. data/spec/notation/parsing/convenience_methods_spec.rb +99 -0
  113. data/spec/notation/parsing/duration_nodes_spec.rb +83 -0
  114. data/spec/notation/parsing/duration_parsing_spec.rb +70 -0
  115. data/spec/notation/parsing/link_nodes_spec.rb +30 -0
  116. data/spec/notation/parsing/link_parsing_spec.rb +13 -0
  117. data/spec/notation/parsing/meter_parsing_spec.rb +23 -0
  118. data/spec/notation/parsing/note_node_spec.rb +87 -0
  119. data/spec/notation/parsing/note_parsing_spec.rb +46 -0
  120. data/spec/notation/parsing/numbers/nonnegative_float_spec.rb +28 -0
  121. data/spec/notation/parsing/numbers/nonnegative_integer_spec.rb +11 -0
  122. data/spec/notation/parsing/numbers/nonnegative_rational_spec.rb +11 -0
  123. data/spec/notation/parsing/numbers/positive_float_spec.rb +28 -0
  124. data/spec/notation/parsing/numbers/positive_integer_spec.rb +28 -0
  125. data/spec/notation/parsing/numbers/positive_rational_spec.rb +28 -0
  126. data/spec/notation/parsing/pitch_node_spec.rb +38 -0
  127. data/spec/notation/parsing/pitch_parsing_spec.rb +14 -0
  128. data/spec/notation/parsing/segment_parsing_spec.rb +27 -0
  129. data/spec/notation/util/value_computer_spec.rb +146 -0
  130. data/spec/performance/conversion/glissando_converter_spec.rb +93 -0
  131. data/spec/performance/conversion/note_sequence_extractor_spec.rb +230 -0
  132. data/spec/performance/conversion/portamento_converter_spec.rb +91 -0
  133. data/spec/performance/conversion/score_collator_spec.rb +183 -0
  134. data/spec/performance/midi/midi_util_spec.rb +110 -0
  135. data/spec/performance/midi/part_sequencer_spec.rb +40 -0
  136. data/spec/performance/midi/score_sequencer_spec.rb +50 -0
  137. data/spec/performance/model/note_sequence_spec.rb +147 -0
  138. data/spec/performance/util/note_linker_spec.rb +68 -0
  139. data/spec/performance/util/optimization_spec.rb +73 -0
  140. data/spec/spec_helper.rb +43 -0
  141. 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