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